bug_bunny 4.6.0 → 4.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/pr.md +42 -0
  3. data/.claude/commands/release.md +41 -0
  4. data/.claude/commands/rubocop.md +22 -0
  5. data/.claude/commands/test.md +28 -0
  6. data/.claude/commands/yard.md +46 -0
  7. data/CHANGELOG.md +42 -15
  8. data/CLAUDE.md +228 -0
  9. data/README.md +154 -221
  10. data/Rakefile +19 -3
  11. data/docs/concepts.md +140 -0
  12. data/docs/howto/controller.md +194 -0
  13. data/docs/howto/middleware_client.md +119 -0
  14. data/docs/howto/middleware_consumer.md +127 -0
  15. data/docs/howto/rails.md +214 -0
  16. data/docs/howto/resource.md +200 -0
  17. data/docs/howto/routing.md +133 -0
  18. data/docs/howto/testing.md +259 -0
  19. data/docs/howto/tracing.md +119 -0
  20. data/lib/bug_bunny/client.rb +41 -18
  21. data/lib/bug_bunny/configuration.rb +63 -0
  22. data/lib/bug_bunny/consumer.rb +51 -37
  23. data/lib/bug_bunny/consumer_middleware.rb +14 -5
  24. data/lib/bug_bunny/controller.rb +29 -4
  25. data/lib/bug_bunny/exception.rb +4 -0
  26. data/lib/bug_bunny/observability.rb +24 -3
  27. data/lib/bug_bunny/resource.rb +31 -21
  28. data/lib/bug_bunny/routing/route.rb +6 -1
  29. data/lib/bug_bunny/routing/route_set.rb +30 -3
  30. data/lib/bug_bunny/session.rb +18 -11
  31. data/lib/bug_bunny/version.rb +1 -1
  32. data/lib/bug_bunny.rb +1 -0
  33. data/mejoras.md +33 -0
  34. data/plan_test.txt +63 -0
  35. data/spec/integration/client_spec.rb +117 -0
  36. data/spec/integration/consumer_middleware_spec.rb +86 -0
  37. data/spec/integration/controller_spec.rb +140 -0
  38. data/spec/integration/error_handling_spec.rb +57 -0
  39. data/spec/integration/infrastructure_spec.rb +52 -0
  40. data/spec/integration/resource_spec.rb +113 -0
  41. data/spec/spec_helper.rb +70 -0
  42. data/spec/support/bunny_mocks.rb +18 -0
  43. data/spec/support/integration_helper.rb +87 -0
  44. data/spec/unit/client_session_pool_spec.rb +159 -0
  45. data/spec/unit/configuration_spec.rb +164 -0
  46. data/spec/unit/consumer_middleware_spec.rb +129 -0
  47. data/spec/unit/consumer_spec.rb +90 -0
  48. data/spec/unit/controller_after_action_spec.rb +155 -0
  49. data/spec/unit/observability_spec.rb +167 -0
  50. data/spec/unit/resource_attributes_spec.rb +69 -0
  51. data/spec/unit/session_spec.rb +98 -0
  52. metadata +36 -3
  53. data/sig/bug_bunny.rbs +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c9f75619decb8d892d2bfe8c56424822672b6b6b5f3be85442be1399c38e76a
4
- data.tar.gz: 7fc1e66a10f46d0a6120dd6164b10b0f1b30d2ff91e8d5daed4358f771f2cc49
3
+ metadata.gz: b3b2260d4ec9ac9684adce0a4ea1c8ab7d86508a601eba51e630438ba5c36059
4
+ data.tar.gz: c8a41ed643ba5293babfe946476fce1c022137916dfe534224159574928e8b08
5
5
  SHA512:
6
- metadata.gz: fd5f4141f5a7892f84d5b3894535632fe63ce24320637a5896530b025c4f3dc8f002f75c41f7cbec57258bca857d58cd1d531c0abf3d6f2047e049cc6119ae98
7
- data.tar.gz: c13414d24ee769acb483fe4a9ee1f9aa2073ea6bf8bea81e1ac290499ad45cd85471ffd022a304bb6da9cc56e737f868bc441870a1b084b05a60892c6f3b6366
6
+ metadata.gz: 6e8ef4dc6d1e9fc6aa4ac067da0aa48ccf5a291c91cb2c8474ed5811e34a57cbbeb44b9b6a813de8d5158a3351a96c15513c1e0715adf5a47e12ef70681935d4
7
+ data.tar.gz: a036a4e0a8c8b8fc29286f5490be2c506561951167c765461ef668b79c77dfcf0bb8e7f2020df28847820875697008129f24e4a97f34079cb54d5202a6e36d65
@@ -0,0 +1,42 @@
1
+ Create a Pull Request for BugBunny via GitHub CLI. Usage: /pr
2
+
3
+ Creá un PR desde la rama actual hacia `main` usando `gh`.
4
+
5
+ ## Pasos
6
+
7
+ 1. Verificá que hay commits en la rama que no están en main: `git log main..HEAD --oneline`
8
+ 2. Revisá todos los cambios del PR: `git diff main...HEAD`
9
+ 3. Determiná el tipo de cambio (feature, bugfix, refactor, docs, chore)
10
+ 4. Creá el PR con `gh pr create`:
11
+
12
+ ```bash
13
+ gh pr create --title "tipo: descripción breve" --body "$(cat <<'EOF'
14
+ ## Summary
15
+ - Bullet points de los cambios principales
16
+
17
+ ## Test plan
18
+ - [ ] Tests existentes pasan (`/test`)
19
+ - [ ] RuboCop sin offenses (`/rubocop`)
20
+ - [ ] YARD documentado en métodos nuevos/modificados
21
+
22
+ ## Notes
23
+ Contexto adicional si es necesario.
24
+
25
+ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
26
+ EOF
27
+ )"
28
+ ```
29
+
30
+ ## Convenciones de título
31
+
32
+ - `feat: descripción` — feature nueva
33
+ - `fix: descripción` — bugfix
34
+ - `chore: descripción` — mantenimiento, deps, config
35
+ - `docs: descripción` — solo documentación
36
+ - `refactor: descripción` — refactor sin cambio de comportamiento
37
+
38
+ ## Importante
39
+
40
+ - El remote SSH está roto — si el PR requiere push previo, usar HTTPS temporalmente
41
+ - Mostrar la URL del PR creado al usuario
42
+ - No crear el PR sin confirmación del usuario
@@ -0,0 +1,41 @@
1
+ Release BugBunny gem. Usage: /release [patch|minor|major]
2
+
3
+ Ejecutá el flujo completo de release para BugBunny. El argumento determina el tipo de bump:
4
+ - `patch` → bugfix (4.6.1 → 4.6.2)
5
+ - `minor` → feature nueva (4.6.1 → 4.7.0)
6
+ - `major` → breaking change (4.6.1 → 5.0.0)
7
+
8
+ ## Pasos
9
+
10
+ 1. **Leer versión actual** de `lib/bug_bunny/version.rb`
11
+ 2. **Calcular nueva versión** según el tipo de bump
12
+ 3. **Actualizar `lib/bug_bunny/version.rb`** con la nueva versión
13
+ 4. **Agregar entrada al tope de `CHANGELOG.md`** con formato:
14
+ ```
15
+ ## [X.Y.Z] - YYYY-MM-DD
16
+ ### ✨ New Features / 🐛 Bug Fixes / 💥 Breaking Changes
17
+ * Descripción de los cambios
18
+ ```
19
+ 5. **Mostrar el diff completo** al usuario y pedir confirmación antes de continuar
20
+ 6. **Commit** con mensaje: `feat|fix|chore: descripción breve vX.Y.Z`
21
+ 7. **Merge a main** desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
22
+ 8. **Push via HTTPS**:
23
+ ```bash
24
+ git remote set-url origin https://github.com/gedera/bug_bunny.git
25
+ git push origin main
26
+ git remote set-url origin git@github.com:gedera/bug_bunny.git
27
+ ```
28
+ 9. **Tag y push**:
29
+ ```bash
30
+ git tag vX.Y.Z
31
+ git remote set-url origin https://github.com/gedera/bug_bunny.git
32
+ git push origin vX.Y.Z
33
+ git remote set-url origin git@github.com:gedera/bug_bunny.git
34
+ ```
35
+
36
+ ## Importante
37
+
38
+ - Nunca commitear ni pushear sin confirmación explícita del usuario
39
+ - El worktree de main está en `/Users/gabriel/src/gems/bug_bunny`
40
+ - SSH está roto — siempre usar HTTPS para push y restaurar SSH después
41
+ - Sourcear chruby antes de cualquier comando Ruby: `source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8`
@@ -0,0 +1,22 @@
1
+ Run RuboCop for BugBunny. Usage: /rubocop [--fix]
2
+
3
+ Ejecutá RuboCop con rubocop-rails-omakase sobre el código modificado.
4
+
5
+ ## Comandos
6
+
7
+ Sin argumentos — solo reporte:
8
+ ```bash
9
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rubocop
10
+ ```
11
+
12
+ Con `--fix` — autocorrect seguro:
13
+ ```bash
14
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rubocop -a
15
+ ```
16
+
17
+ ## Reglas importantes
18
+
19
+ - **Solo corregir código nuevo o modificado en el PR actual** — nunca tocar código existente no relacionado
20
+ - Si hay offenses que requieren intervención manual (no autocorregibles), reportalos con línea y descripción
21
+ - rubocop-rails-omakase tiene opiniones fuertes sobre estilo — seguirlas sin discutir
22
+ - Si hay un offense legítimo que debe ignorarse, usar `# rubocop:disable Cop/Name` en la línea específica con un comentario explicativo
@@ -0,0 +1,28 @@
1
+ Run RSpec tests for BugBunny. Usage: /test [path]
2
+
3
+ Ejecutá la suite de tests de BugBunny con Ruby 3.3.8.
4
+
5
+ ## Comando base
6
+
7
+ ```bash
8
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rspec
9
+ ```
10
+
11
+ Si se pasa un path como argumento, corré solo ese archivo o directorio:
12
+
13
+ ```bash
14
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec rspec $ARGUMENTS
15
+ ```
16
+
17
+ ## Después de correr los tests
18
+
19
+ - Si hay failures: analizá el error, identificá la causa raíz, proponé el fix al usuario antes de tocar código
20
+ - Si hay warnings de deprecación: reportalos al usuario
21
+ - Si todos pasan: confirmá con el conteo de ejemplos y tiempo de ejecución
22
+
23
+ ## Convenciones de RSpec en este proyecto
24
+
25
+ - Tests en `spec/`
26
+ - Sin mocks de RabbitMQ real — usar doubles de Bunny
27
+ - Describir comportamiento, no implementación
28
+ - Un `context` por escenario, `it` con descripción en español o inglés consistente con el archivo
@@ -0,0 +1,46 @@
1
+ Validate and generate YARD documentation for BugBunny. Usage: /yard
2
+
3
+ Verificá y generá la documentación YARD de la gema.
4
+
5
+ ## Comandos
6
+
7
+ Generar docs:
8
+ ```bash
9
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec yard doc
10
+ ```
11
+
12
+ Ver métodos sin documentar:
13
+ ```bash
14
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8 && bundle exec yard stats --list-undoc
15
+ ```
16
+
17
+ ## Estándar YARD de este proyecto
18
+
19
+ Todo método público nuevo o modificado debe tener:
20
+
21
+ ```ruby
22
+ # Descripción breve en una línea.
23
+ #
24
+ # Descripción extendida opcional si la firma no es autoexplicativa.
25
+ #
26
+ # @param nombre [Tipo] Descripción del parámetro
27
+ # @return [Tipo] Descripción del valor de retorno
28
+ # @raise [ClaseError] Condición bajo la cual se lanza
29
+ # @example
30
+ # resultado = mi_metodo(arg)
31
+ def mi_metodo(nombre)
32
+ ```
33
+
34
+ ## Tipos comunes en este proyecto
35
+
36
+ - `[String]`, `[Integer]`, `[Boolean]`, `[Hash]`, `[Array]`, `[Symbol]`
37
+ - `[Bunny::Session]`, `[Bunny::Channel]`, `[Bunny::MessageProperties]`
38
+ - `[BugBunny::Session]`, `[BugBunny::Request]`, `[BugBunny::Configuration]`
39
+ - `[Proc, nil]` para callbacks opcionales
40
+ - `[void]` para métodos sin return value relevante
41
+
42
+ ## Después de correr
43
+
44
+ - Reportá los métodos públicos sin documentar
45
+ - No documentar métodos privados (YARD los ignora por defecto con `private`)
46
+ - No documentar métodos triviales (`attr_reader`, `attr_accessor`) salvo que necesiten contexto
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.7.0] - 2026-04-01
4
+
5
+ ### ✨ New Features
6
+ * **Routing — Namespace blocks:** Nuevo método `namespace` en el DSL de rutas para organizar controladores en módulos Ruby. Los namespaces son acumulativos y anidables: `namespace :api { namespace :v1 { resources :metrics } }` resuelve a `Api::V1::MetricsController`. El namespace de la ruta tiene precedencia sobre `config.controller_namespace`.
7
+ * **Controller — `after_action`:** Nuevo callback que se ejecuta después de la acción exitosa. No se invoca si un `before_action` haltó la cadena ni si la acción lanzó una excepción, siguiendo el comportamiento de Rails. Soporta `only:` y `except:`, y se hereda entre controladores.
8
+ * **Controller — `render` con `headers:`:** El método `render` acepta un keyword `headers:` para adjuntar headers por-respuesta sin mutar `response_headers`. `response_headers` se inicializa con `with_indifferent_access`.
9
+ * **Consumer — `shutdown`:** Nuevo método público que detiene el health check timer y cierra el canal AMQP de forma ordenada. Se invoca automáticamente vía `ensure` cuando `subscribe` termina por cualquier motivo (señal, error, fin de loop), garantizando limpieza completa de recursos.
10
+ * **Configuration — `validate!`:** `BugBunny.configure` invoca `validate!` al final del bloque. Verifica presencia de campos requeridos (`host`, `port`, `username`, `password`, `vhost`) y rangos válidos para timeouts y `channel_prefetch`. Lanza `BugBunny::ConfigurationError` con mensaje descriptivo en lugar de fallar silenciosamente al conectar.
11
+
12
+ ### ⚡ Performance & Robustness
13
+ * **Client — Session & Producer pooling:** `BugBunny::Client` ya no crea ni destruye un `Session` (canal AMQP) y un `Producer` por request. Ambos se cachean como ivars sobre el objeto conexión del pool (`@_bug_bunny_session`, `@_bug_bunny_producer`) y se reutilizan en todos los requests del mismo slot. El cacheo del `Producer` es crítico: el Producer registra un `basic_consume` en el canal para escuchar replies RPC; recrearlo sobre un canal reutilizado intentaría un segundo `basic_consume` causando un error AMQP. Thread-safe sin mutex adicional: `ConnectionPool` garantiza que cada slot es usado por un único thread a la vez.
14
+ * **Session — Double-checked locking:** El método `channel` usa un patrón de double-checked locking con `@channel_mutex` para evitar que múltiples threads creen canales simultáneamente cuando el canal cae. `close` también está protegido por el mismo mutex.
15
+ * **ConsumerMiddleware::Stack — Thread safety:** `use`, `empty?` y `call` están protegidos por un `Mutex`. `call` toma un snapshot del array bajo mutex y ejecuta la cadena fuera del lock, evitando serializar el procesamiento de mensajes durante registros concurrentes.
16
+
17
+ ### 🔍 Observability
18
+ * **`Observability::SENSITIVE_KEYS` expandido:** La lista de claves filtradas en logs crece de 5 a 11 entradas: se agregan `authorization`, `credential`, `private_key`, `csrf`, `session_id` y `passwd`. El matching pasa de comparación exacta por symbol a substring matching en lowercase con normalización de hyphens a underscores, cubriendo variantes como `X-Api-Key`, `user_password` o `accessToken`.
19
+ * **`Observability.sensitive_key?` público:** El método de detección de claves sensibles se expone como método de módulo reutilizable por componentes externos (middlewares, integraciones).
20
+
21
+ ### 🐛 Bug Fixes
22
+ * **Resource — dirty tracking híbrido:** Se corrigen los overrides `changed?` y `changed` para combinar correctamente el tracking nativo de `ActiveModel::Dirty` (atributos tipados) con el tracking manual de atributos dinámicos (`@dynamic_changes`). Anteriormente, `changed?` solo reflejaba atributos tipados. `id=` ahora registra el cambio en `@dynamic_changes` cuando `id` no está declarado como `attribute`.
23
+ * **Resource — inicialización:** `initialize` pasa `attributes` directamente a `super` en lugar del patrón `super() + assign_attributes`, delegando correctamente a `ActiveModel::Model`.
24
+
25
+ ## [4.6.1] - 2026-03-31
26
+
27
+ ### 🐛 Bug Fixes
28
+ * **Observability:** `safe_log` ahora serializa valores `Hash` siempre como JSON compacto (`val.to_json`), sin pasar por `.inspect`. Anteriormente, si el JSON del Hash contenía espacios, se llamaba `.inspect` produciendo strings escapados como `"{\"exclusive\":false}"` en lugar del objeto JSON esperado `{"exclusive":false}`. Afectaba a los eventos `consumer.start` (campo `queue_opts`) y `consumer.bound` (campo `exchange_opts`).
29
+
3
30
  ## [4.6.0] - 2026-03-31
4
31
 
5
32
  ### ✨ New Features
@@ -73,33 +100,33 @@
73
100
 
74
101
  ## [4.2.0] - 2026-03-22
75
102
 
76
- ### 🔠Observability & Structured Logging
77
- * **Structured Logs (Key-Value):** Se migraron todos los logs del framework a un formato \`key=value\` estructurado, ideal para herramientas de monitoreo como Datadog o CloudWatch. Se eliminaron emojis y texto libre para mejorar el parseo automático.
78
- * **Lazy Evaluation (Debug Blocks):** Las llamadas a \`logger.debug\` ahora utilizan bloques para evitar la interpolación de strings innecesaria en producción, optimizando el uso de CPU y memoria.
103
+ ### Observability & Structured Logging
104
+ * **Structured Logs (Key-Value):** Se migraron todos los logs del framework a un formato `key=value` estructurado, ideal para herramientas de monitoreo como Datadog o CloudWatch. Se eliminaron emojis y texto libre para mejorar el parseo automático.
105
+ * **Lazy Evaluation (Debug Blocks):** Las llamadas a `logger.debug` ahora utilizan bloques para evitar la interpolación de strings innecesaria en producción, optimizando el uso de CPU y memoria.
79
106
 
80
- ### ðŸ›¡ï¸ Resilience & Connectivity
81
- * **Exponential Backoff:** El \`Consumer\` ahora implementa un algoritmo de reintento exponencial para reconectarse a RabbitMQ, evitando picos de carga durante caídas del broker.
82
- * **Max Reconnect Attempts:** Nueva configuración \`max_reconnect_attempts\` que permite que el worker falle definitivamente tras N intentos, facilitando el reinicio del Pod por parte de orquestadores como Kubernetes.
83
- * **Performance Tuning:** Se desactivaron los \`publisher_confirms\` en el canal del \`Consumer\` al responder RPCs para reducir la latencia de respuesta (round-trips innecesarios).
107
+ ### Resilience & Connectivity
108
+ * **Exponential Backoff:** El `Consumer` ahora implementa un algoritmo de reintento exponencial para reconectarse a RabbitMQ, evitando picos de carga durante caídas del broker.
109
+ * **Max Reconnect Attempts:** Nueva configuración `max_reconnect_attempts` que permite que el worker falle definitivamente tras N intentos, facilitando el reinicio del Pod por parte de orquestadores como Kubernetes.
110
+ * **Performance Tuning:** Se desactivaron los `publisher_confirms` en el canal del `Consumer` al responder RPCs para reducir la latencia de respuesta (round-trips innecesarios).
84
111
 
85
112
  ## [4.1.2] - 2026-03-22
86
113
 
87
- ### Improvements
88
- * **Controller:** Ahora lanza una excepción \`BugBunny::BadRequest\` (400) si el cuerpo de la petición contiene un JSON inválido, mejorando la depuración en el cliente.
89
- * **Resource:** Se añadió una protección a \`.with\` (\`ScopeProxy\`) para asegurar que el contexto sea de un solo uso, evitando efectos secundarios en llamadas encadenadas.
114
+ ### Improvements
115
+ * **Controller:** Ahora lanza una excepción `BugBunny::BadRequest` (400) si el cuerpo de la petición contiene un JSON inválido, mejorando la depuración en el cliente.
116
+ * **Resource:** Se añadió una protección a `.with` (`ScopeProxy`) para asegurar que el contexto sea de un solo uso, evitando efectos secundarios en llamadas encadenadas.
90
117
 
91
118
  ## [4.1.1] - 2026-03-22
92
119
 
93
120
  ### 🐛 Bug Fixes
94
- * **Consumer:** Previene memory leak al detener el `TimerTask` de health check previo antes de realizar una reconexión.
95
- * **Controller:** Corrige la mutación accidental de \`log_tags\` globales al usar una lógica de herencia no destructiva en \`compute_tags\`.
121
+ * **Consumer:** Previene memory leak al detener el `TimerTask` de health check previo antes de realizar una reconexión.
122
+ * **Controller:** Corrige la mutación accidental de `log_tags` globales al usar una lógica de herencia no destructiva en `compute_tags`.
96
123
 
97
124
  ## [4.1.0] - 2026-03-22
98
125
 
99
126
  ### 🚀 New Features & Improvements
100
- * **Faraday-style Client API:** Se introdujo el método \`Client#send\` como punto de entrada genérico, permitiendo una sintaxis más familiar y flexible.
101
- * **Flexible Delivery Modes:** Introducción del atributo \`delivery_mode\` (:rpc o :publish). Ahora es posible configurar la estrategia de envío a nivel de cliente o por cada petición individual.
102
- * **Smart Request Defaults:** Los métodos \`request\` y \`publish\` ahora delegan internamente en \`send\`, manteniendo la compatibilidad pero beneficiándose de la nueva arquitectura de peticiones.
127
+ * **Faraday-style Client API:** Se introdujo el método `Client#send` como punto de entrada genérico, permitiendo una sintaxis más familiar y flexible.
128
+ * **Flexible Delivery Modes:** Introducción del atributo `delivery_mode` (:rpc o :publish). Ahora es posible configurar la estrategia de envío a nivel de cliente o por cada petición individual.
129
+ * **Smart Request Defaults:** Los métodos `request` y `publish` ahora delegan internamente en `send`, manteniendo la compatibilidad pero beneficiándose de la nueva arquitectura de peticiones.
103
130
 
104
131
  ## [4.0.1] - 2026-03-13
105
132
 
data/CLAUDE.md ADDED
@@ -0,0 +1,228 @@
1
+ # BugBunny — Project Intelligence
2
+
3
+ ## ¿Qué es BugBunny?
4
+
5
+ BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre AMQP (RabbitMQ). Permite que microservicios se comuniquen via RabbitMQ usando patrones familiares de HTTP: verbos (GET, POST, PUT, DELETE), controladores, rutas declarativas, RPC síncrono y fire-and-forget.
6
+
7
+ **Problema que resuelve:** Eliminar el acoplamiento directo entre microservicios via HTTP, usando RabbitMQ como bus de mensajes con la misma ergonomía de un framework web.
8
+
9
+ ## Arquitectura
10
+
11
+ ```
12
+ Publisher (Client/Resource)
13
+ └─ Producer → Session → Bunny → RabbitMQ Exchange
14
+
15
+ RabbitMQ Queue
16
+
17
+ Consumer (subscribe loop)
18
+ └─ ConsumerMiddleware::Stack
19
+ └─ process_message
20
+ └─ Router → Controller → Action
21
+ └─ reply (RPC)
22
+ ```
23
+
24
+ ### Componentes clave
25
+
26
+ | Clase | Responsabilidad |
27
+ |---|---|
28
+ | `BugBunny::Session` | Wrapper de canal Bunny. Declara exchanges y queues. |
29
+ | `BugBunny::Consumer` | Subscribe loop. Rutea mensajes a controladores via `BugBunny.routes`. |
30
+ | `BugBunny::ConsumerMiddleware::Stack` | Pipeline de middlewares antes de `process_message`. |
31
+ | `BugBunny::Producer` | Publica mensajes. Implementa RPC con `Concurrent::IVar`. |
32
+ | `BugBunny::Client` | API de alto nivel para el publicador. Pool de conexiones. |
33
+ | `BugBunny::Controller` | Base class tipo Rails. `around_action`, `before_action`, `render`. |
34
+ | `BugBunny::Resource` | ActiveRecord-like sobre AMQP. `find`, `where`, `create`, etc. |
35
+ | `BugBunny::Request` | Value object del mensaje saliente (path, method, params, headers). |
36
+ | `BugBunny::Observability` | Mixin de logging estructurado. `safe_log`, `exception_metadata`. |
37
+ | `BugBunny::Configuration` | Configuración global. Logger, timeouts, middleware hooks. |
38
+
39
+ ### Flujo RPC completo
40
+
41
+ 1. `Resource.find(id)` → `Client#request` → `Producer#rpc`
42
+ 2. Producer publica en exchange con `reply_to: 'amq.rabbitmq.reply-to'`
43
+ 3. `Concurrent::IVar` bloquea el thread principal (`future.value(timeout)`)
44
+ 4. Consumer recibe → middleware stack → controller → `reply(response)`
45
+ 5. Reply listener thread setea `future.set({ body:, headers: })`
46
+ 6. Thread principal: `on_rpc_reply&.call(headers)` → `parse_response(body)`
47
+
48
+ ## Hooks de extensión
49
+
50
+ ```ruby
51
+ # Middleware antes de process_message (ej: tracing, auth)
52
+ BugBunny.consumer_middlewares.use MyMiddleware
53
+
54
+ # Headers a inyectar en el reply RPC (ej: trace context actualizado)
55
+ config.rpc_reply_headers = -> { { 'X-Amzn-Trace-Id' => Tracer.header } }
56
+
57
+ # Callback en el thread principal al recibir el reply (ej: hidratar tracer)
58
+ config.on_rpc_reply = ->(headers) { Tracer.hydrate(headers['X-Amzn-Trace-Id']) }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Dominio y Expertise
64
+
65
+ Al trabajar en esta gema aplicá expertise en:
66
+
67
+ - **Ruby idiomático**: módulos, mixins, metaprogramación, `class_attribute`, `Concurrent::*`
68
+ - **RabbitMQ / AMQP**: exchanges (direct/topic/fanout), queues, bindings, `reply_to`, `correlation_id`, `properties.headers`, publisher confirms, manual ack
69
+ - **Bunny**: la gema Ruby que wrappea AMQP. `channel`, `basic_consume`, `basic_publish`, `IVar`
70
+ - **Rails patterns**: `ActiveModel`, `ActiveSupport`, `class_attribute`, `concerns`, `constantize`
71
+ - **Rack**: `Rack::Utils.parse_nested_query`, `build_nested_query`
72
+
73
+ ---
74
+
75
+ ## Observability — Estándar de Logging
76
+
77
+ Esta gema implementa su propio patrón de observability via `BugBunny::Observability`.
78
+
79
+ ### Reglas fundamentales
80
+
81
+ - **Formato**: `component=x event=clase.evento [key=value ...]` — todo en una línea
82
+ - **Nunca** llamar al logger directamente. Siempre usar `safe_log`
83
+ - **Nunca** `Kernel#warn`, `$stderr`, `puts`
84
+ - **Niveles**: `ERROR`=excepción, `WARN`=inesperado+continuó, `INFO`=normal, `DEBUG`=detalle
85
+ - `DEBUG` siempre en bloque: `logger.debug { "k=#{v}" }` — `safe_log` lo maneja internamente
86
+ - Duraciones: `Process.clock_gettime(Process::CLOCK_MONOTONIC)`, nunca `Time.now`
87
+ - Logger failures **nunca** interrumpen el flujo — `safe_log` tiene `rescue StandardError`
88
+
89
+ ### Uso en clases nuevas
90
+
91
+ ```ruby
92
+ class BugBunny::MiClase
93
+ include BugBunny::Observability
94
+
95
+ def initialize
96
+ @logger = BugBunny.configuration.logger
97
+ end
98
+
99
+ def mi_metodo
100
+ start = monotonic_now
101
+ # ...
102
+ safe_log(:info, "mi_clase.mi_evento", campo: valor, duration_s: duration_s(start))
103
+ rescue StandardError => e
104
+ safe_log(:error, "mi_clase.error", **exception_metadata(e))
105
+ end
106
+ end
107
+ ```
108
+
109
+ ### Naming de eventos
110
+
111
+ Formato estricto: `"clase.evento"` (string, nunca symbol)
112
+
113
+ | Evento | Nivel | Cuándo |
114
+ |---|---|---|
115
+ | `consumer.start` | INFO | Consumer inicia subscribe |
116
+ | `consumer.bound` | INFO | Queue bindeada al exchange |
117
+ | `consumer.message_received` | INFO | Mensaje recibido, antes del routing |
118
+ | `consumer.route_matched` | DEBUG | Ruta encontrada |
119
+ | `consumer.message_processed` | INFO | Procesamiento exitoso con duración |
120
+ | `consumer.execution_error` | ERROR | Excepción en el procesamiento |
121
+ | `producer.publish` | INFO | Mensaje publicado |
122
+ | `producer.rpc_waiting` | DEBUG | Bloqueando esperando respuesta |
123
+ | `producer.rpc_response_received` | DEBUG | Reply recibido (thread principal) |
124
+
125
+ ### Campos estándar
126
+
127
+ ```ruby
128
+ safe_log(:error, "clase.error", **exception_metadata(e))
129
+ # => error_class: "RuntimeError", error_message: "..."
130
+
131
+ safe_log(:info, "clase.evento", duration_s: duration_s(start_time))
132
+ # => duration_s: 0.001234
133
+
134
+ # Valores sensibles se filtran automáticamente:
135
+ # password, token, secret, api_key, auth → [FILTERED]
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Standards de Código
141
+
142
+ ### RuboCop
143
+
144
+ Esta gema usa **rubocop-rails-omakase**. Todo código nuevo o modificado debe cumplir.
145
+
146
+ ```bash
147
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
148
+ bundle exec rubocop
149
+ bundle exec rubocop -a # autocorrect
150
+ ```
151
+
152
+ **No corregir código existente no tocado.** Solo el código nuevo o modificado en el PR.
153
+
154
+ ### YARD
155
+
156
+ Todo método público nuevo o modificado lleva documentación YARD:
157
+
158
+ ```ruby
159
+ # Descripción breve.
160
+ #
161
+ # Descripción extendida si es necesario.
162
+ #
163
+ # @param name [Type] Descripción
164
+ # @return [Type] Descripción
165
+ # @raise [ErrorClass] Cuándo se lanza
166
+ # @example
167
+ # resultado = mi_metodo(arg)
168
+ def mi_metodo(name)
169
+ ```
170
+
171
+ ```bash
172
+ bundle exec yard doc
173
+ bundle exec yard stats --list-undoc
174
+ ```
175
+
176
+ ### RSpec
177
+
178
+ Tests en `spec/`. Sin mocks de dependencias externas reales (RabbitMQ se mockea con doubles de Bunny).
179
+
180
+ ```bash
181
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
182
+ bundle exec rspec
183
+ bundle exec rspec spec/bug_bunny/consumer_spec.rb # archivo específico
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Entorno de Desarrollo
189
+
190
+ ### Ruby
191
+
192
+ ```bash
193
+ source /opt/homebrew/opt/chruby/share/chruby/chruby.sh && chruby ruby-3.3.8
194
+ ```
195
+
196
+ Nunca usar `bundle exec ruby` con el Ruby del sistema (2.6). Siempre sourcear chruby primero.
197
+
198
+ ### Worktrees
199
+
200
+ - **Main**: `/Users/gabriel/src/gems/bug_bunny` (rama `main`)
201
+ - **Work**: `/Users/gabriel/src/gems/worktrees/current-5n3` (ramas de feature)
202
+ - `main` está checkeado en otro worktree — no se puede hacer `git checkout main` desde el worktree de trabajo
203
+
204
+ ### Push a remoto
205
+
206
+ SSH está roto en este entorno. Para push siempre:
207
+
208
+ ```bash
209
+ git remote set-url origin https://github.com/gedera/bug_bunny.git
210
+ git push origin main
211
+ git remote set-url origin git@github.com:gedera/bug_bunny.git # restaurar
212
+ ```
213
+
214
+ ---
215
+
216
+ ## Release Workflow
217
+
218
+ Usá el comando `/release` para el flujo completo. Manualmente:
219
+
220
+ 1. Determinar tipo: `patch`=bugfix, `minor`=feature nueva, `major`=breaking change
221
+ 2. Actualizar `lib/bug_bunny/version.rb`
222
+ 3. Agregar entrada al tope de `CHANGELOG.md`
223
+ 4. Commit con mensaje convencional
224
+ 5. Desde `/Users/gabriel/src/gems/bug_bunny`: `git merge --ff-only <branch>`
225
+ 6. Push via HTTPS + restaurar SSH
226
+ 7. `git tag vX.Y.Z && git push origin vX.Y.Z`
227
+
228
+ **Nunca commitear ni pushear sin permiso explícito del usuario.**