bug_bunny 4.17.0 → 4.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec5013f4d782766d388826265255b008a9c17119b15acef95a05fcb01a9722d5
4
- data.tar.gz: 0cd930d1d4c982a0ba9bf73e9562504d0e776c88c760e427dc440a1399b57a13
3
+ metadata.gz: 50bb70f17d783b52abeb208bc362953c9494b61853f836da84f73552e3a76966
4
+ data.tar.gz: 37f9550b64942b1e544a6e7d593c1695f1293c6a1b488e1b0bec3179d1f3b7a4
5
5
  SHA512:
6
- metadata.gz: 800c22370b7e39812cc1d90ca74db9ea4736e4354e4061d39a0e0e9d5fbb7fa5ff05c56474f61056a7fe072144339c48d725f040c34c16ba7f0805a314881d66
7
- data.tar.gz: 921e0359f5426db49beaf327e67885568313282c3972e9bc2e9d7f88e28fdad032f3258ab73e4cca16f9b3f70cdbf5f42311e3d4f910e4430494ebb4ea8e05a8
6
+ metadata.gz: 4733c50dcb6824c4e7d53bc0842e823b0674c13bf6cd9d963f99b6fa090166d083ca64cc6e65eba2fc29bff09be840772a6404fae19e62f3dd9b6dbcf56fc2d3
7
+ data.tar.gz: e9613f65b4bce0ad19aa8c19f259c6a3069ba4dfe185e4146a5992ddfbd20b3428816ed882e9d637f2b4a85d1d5d307094763bf8f3082900e8c6d9d3fe8a1fb7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.17.1] - 2026-05-19
4
+
5
+ > Release docs-only sobre 4.17.0 — sin cambios en `lib/` ni API pública. Incorpora la familia de skills `dev-*` (RFC-001) y los artefactos de detalle version-locked (`docs/glossary`, `docs/behavior`) que viajan en el `.gem`.
6
+
7
+ ### Nuevas funcionalidades
8
+ - Incorporar familia de skills `dev-*` (glosario + comportamiento) y recomponer composites (#47) — @Gabriel
9
+
10
+ ### Correcciones
11
+ - Consistencia README: Quickstart Consumer, `rpc_timeout`, idioma, coexistencia — @Gabriel
12
+ - Escapar `;` en Note del diagrama Mermaid (flujo confirmed) — @Gabriel
13
+
14
+ ### Documentación
15
+ - Marcar verificación humana de `behavior.md` completada (RFC-001 §3.3) — @Gabriel
16
+ - Rework `skill/SKILL.md` a RFC-008 endurecido (dev-compose 2.0.0) — @Gabriel
17
+ - Alinear nota de coexistencia skill/README — @Gabriel
18
+
19
+ ### Otros cambios
20
+ - Migrar de `service-release` a `gem-release` — @Gabriel
21
+
3
22
  ## [4.17.0] - 2026-05-13
4
23
 
5
24
  ### Nuevas funcionalidades
data/CLAUDE.md CHANGED
@@ -8,14 +8,30 @@ BugBunny es una gema Ruby que implementa una capa de enrutamiento RESTful sobre
8
8
 
9
9
  ## Documentación
10
10
 
11
- - **Para humanos**: `docs/` (5 archivos) + `README.md`. Ver README para índice.
12
- - **Para agentes AI**: `skill/SKILL.md` + `skill/references/`. Es la skill empaquetada que otros proyectos consumen via `skill-manager sync`.
13
- - **Nunca referenciar `skill/` desde `docs/` o `README.md`** son audiencias distintas.
11
+ - **Modelo `dev-*` (RFC-001):** artefactos de detalle en `docs/<capa>/`
12
+ (`data/`, `glossary/`, `behavior/`); compuestos (`README.md` humano,
13
+ `skill/SKILL.md` agente version-locked) **indexan, no duplican**.
14
+ Artefactos generados por `dev-structure` / `dev-enrich`; compuestos por
15
+ `dev-compose`. Verificación humana antes de commitear.
16
+ - **Estado actual:** `docs/data` = n/a (gema sin DB, declarado solo en índice);
17
+ `docs/glossary` parcial (acreta por PR); `docs/behavior` completo (6 flujos,
18
+ backfill on-demand); operaciones/interfaz/topología = dev-structure F2 no
19
+ implementado.
20
+ - **Para agentes AI**: `skill/SKILL.md` (empaquetada en el `.gem`) +
21
+ `skill/references/`.
22
+ - **Coexistencia transitoria con destino pendiente (RFC-008 §2 — interim de
23
+ migración):** contrato/arquitectura sigue embebido en
24
+ `README.md`/`skill/SKILL.md` y las guías how-to viven en `skill/references/`
25
+ (pre-estándar) porque su capa destino (operaciones/interfaz/topología) es
26
+ dev-structure F2 no implementado. Por norma: no se fabrica la capa, no se
27
+ borra el contrato sin destino; migra cuando F2 entregue, mismo PR. Estado
28
+ transitorio declarado en el índice de artefactos. Origen del gap (resuelto,
29
+ normado): `sequre/ai_knowledge#95`.
14
30
 
15
31
  ## Knowledge Base
16
32
  - Las skills en `.agents/skills/` incluyen conocimiento de dependencias.
17
33
  - Leer la skill de una dependencia ANTES de responder sobre ella.
18
- - Rebuild: `ruby .agents/skills/skill-manager/scripts/sync.rb`
34
+ - Rebuild: `wispro-agent sync`
19
35
 
20
36
  ### Entorno
21
37
  - Versión de Ruby: leer `.ruby-version`
data/README.md CHANGED
@@ -58,8 +58,8 @@ module BugBunny
58
58
  end
59
59
 
60
60
  # Worker entrypoint (dedicated thread or process)
61
- consumer = BugBunny::Consumer.new
62
- consumer.subscribe(
61
+ BugBunny::Consumer.subscribe(
62
+ connection: BugBunny.create_connection,
63
63
  queue_name: 'inventory_queue',
64
64
  exchange_name: 'inventory',
65
65
  routing_key: 'nodes'
@@ -124,7 +124,7 @@ BugBunny.configure do |config|
124
124
  config.network_recovery_interval = 5 # seconds, base for exponential backoff
125
125
 
126
126
  # Timeouts
127
- config.rpc_timeout = 30 # seconds, for synchronous RPC calls
127
+ config.rpc_timeout = 10 # seconds (default 10), for synchronous RPC calls
128
128
  config.connection_timeout = 10
129
129
  config.read_timeout = 10
130
130
  config.write_timeout = 10
@@ -339,9 +339,9 @@ BugBunny.consumer_middlewares.use TracingMiddleware
339
339
 
340
340
  ## Observability
341
341
 
342
- BugBunny implementa de forma nativa las [OpenTelemetry semantic conventions for messaging](https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/), inyectando automáticamente campos como `messaging_system`, `messaging_operation`, `messaging_destination_name` y `messaging_message_id` tanto en los headers AMQP como en los log events estructurados.
342
+ BugBunny natively implements the [OpenTelemetry semantic conventions for messaging](https://opentelemetry.io/docs/specs/otel/trace/semantic-conventions/messaging/), automatically injecting fields like `messaging_system`, `messaging_operation`, `messaging_destination_name` and `messaging_message_id` into both the AMQP headers and the structured log events.
343
343
 
344
- Todos los eventos internos se emiten como logs `key=value` compatibles con Datadog, CloudWatch, ELK y ExisRay.
344
+ All internal events are emitted as `key=value` logs compatible with Datadog, CloudWatch, ELK and ExisRay.
345
345
 
346
346
  ```
347
347
  component=bug_bunny event=producer.publish method=POST path=acct/publish messaging_destination_name=acct_x messaging_routing_key=acct.start.42
@@ -353,19 +353,19 @@ component=bug_bunny event=consumer.execution_error error_class=RuntimeError erro
353
353
  component=bug_bunny event=consumer.connection_error attempt_count=2 retry_in_s=10 error_message="..."
354
354
  ```
355
355
 
356
- ### Duraciones medidas internamente
356
+ ### Internally measured durations
357
357
 
358
- BugBunny mide y emite duraciones automáticamente — **no es necesario envolver llamadas a `client.publish` con `Process.clock_gettime` en el código de aplicación**. Las unidades siguen las [OpenTelemetry metric semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/) (`s`, segundos como `Float`).
358
+ BugBunny measures and emits durations automatically — **there is no need to wrap `client.publish` calls with `Process.clock_gettime` in application code**. Units follow the [OpenTelemetry metric semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/) (`s`, seconds as `Float`).
359
359
 
360
- | Evento | Duración | Mide |
360
+ | Event | Duration | Measures |
361
361
  |---|---|---|
362
- | `producer.published` | `duration_s` | Solo el `basic_publish` (TCP enqueue al broker). |
363
- | `producer.confirmed` | `publish_duration_s` + `confirm_duration_s` + `duration_s` (total) | Publish + espera de ACK del broker. |
364
- | `producer.rpc_response_received` | `duration_s` | Round-trip RPC completo (publish + procesamiento remoto + reply). |
365
- | `consumer.message_processed` | `duration_s` | Procesamiento del mensaje (router + controller + reply). |
366
- | `consumer.execution_error` | `duration_s` | Tiempo transcurrido hasta el error. |
362
+ | `producer.published` | `duration_s` | Only the `basic_publish` (TCP enqueue to the broker). |
363
+ | `producer.confirmed` | `publish_duration_s` + `confirm_duration_s` + `duration_s` (total) | Publish + wait for broker ACK. |
364
+ | `producer.rpc_response_received` | `duration_s` | Full RPC round-trip (publish + remote processing + reply). |
365
+ | `consumer.message_processed` | `duration_s` | Message processing (router + controller + reply). |
366
+ | `consumer.execution_error` | `duration_s` | Elapsed time until the error. |
367
367
 
368
- Las claves sensibles (`password`, `token`, `secret`, `api_key`, `authorization`, etc.) se filtran automáticamente a `[FILTERED]` en toda la salida de logs.
368
+ Sensitive keys (`password`, `token`, `secret`, `api_key`, `authorization`, etc.) are automatically filtered to `[FILTERED]` across all log output.
369
369
 
370
370
  ---
371
371
 
@@ -426,17 +426,33 @@ end
426
426
 
427
427
  ---
428
428
 
429
- ## Documentation
430
-
431
- - [Concepts](docs/concepts.md) What BugBunny is, AMQP in 5 minutes, RPC vs fire-and-forget
432
- - [Routing](docs/howto/routing.md) — Full routing DSL reference
433
- - [Controllers](docs/howto/controller.md) Filters, `rescue_from`, `render`, `after_action`
434
- - [Resource ORM](docs/howto/resource.md) — CRUD, typed and dynamic attributes, `.with` scoping
435
- - [Client Middleware](docs/howto/middleware_client.md)Request/response middleware stack
436
- - [Consumer Middleware](docs/howto/middleware_consumer.md) Message processing middleware stack
437
- - [Distributed Tracing](docs/howto/tracing.md) — Propagating trace context through RPC cycles
438
- - [Rails Setup](docs/howto/rails.md)Full integration: Puma, Sidekiq, Zeitwerk, health checks
439
- - [Testing](docs/howto/testing.md) — Unit and integration testing with Bunny mocks
429
+ ## Índice de artefactos
430
+
431
+ Artefactos de detalle (modelo `dev-*`, RFC-001). El README indexa; no duplica.
432
+
433
+ | Capa | Artefacto | Estado |
434
+ |---|---|---|
435
+ | Datos | — | n/agema sin DB (sin schema/models) |
436
+ | Glosario | [docs/glossary/glossary.md](docs/glossary/glossary.md) | parcial, acreta por PR |
437
+ | Comportamiento | [docs/behavior/behavior.md](docs/behavior/behavior.md) | completa 6 flujos (backfill on-demand) |
438
+ | Operaciones / Interfaz / Topología | | F2 no implementado (dev-structure) ver nota |
439
+
440
+ **Coexistencia transitoria con destino pendiente (RFC-008 §2 — interim de migración):** mientras la capa de detalle destino (operaciones/interfaz/topología) esté declarada pero **no implementada** (dev-structure F1, F2 del plan), permanecen embebidos/cruzados, bajo el interim normado:
441
+
442
+ - **En este README:** el contrato (jerarquía de excepciones, API de configuración, modos de entrega).
443
+ - **En `skill/SKILL.md`:** además el diagrama de arquitectura (flujo RPC).
444
+ - **Guías how-to** (`skill/references/*.md`, pre-estándar): el README las enlaza pese a la regla "no referenciar `skill/` desde el README" — destino futuro `docs/howto/`.
445
+
446
+ Por RFC-008 §2: no se fabrica la capa, no se borra contrato sin destino, no se duplica; migra cuando F2 entregue, mismo PR. Estado transitorio declarado, no excepción permanente. Origen del gap (resuelto, normado): [sequre/ai_knowledge#95](https://github.com/sequre/ai_knowledge/issues/95).
447
+
448
+ How-to (pre-estándar):
449
+ [Routing](skill/references/routing.md) ·
450
+ [Controllers](skill/references/controller.md) ·
451
+ [Resources](skill/references/resource.md) ·
452
+ [Client & Middleware](skill/references/client-middleware.md) ·
453
+ [Consumer](skill/references/consumer.md) ·
454
+ [Errores](skill/references/errores.md) ·
455
+ [Testing](skill/references/testing.md)
440
456
 
441
457
  ---
442
458
 
@@ -0,0 +1,176 @@
1
+ # Comportamiento — bug_bunny
2
+
3
+ > meta: artefacto `comportamiento` · RFC-007 (cadencia incremental default / completo on-demand) · generado dev-enrich 1.3.0 (backfill on-demand) · anclado a `a5cdb10` · cobertura: completa (6 flujos) · verificado por humano 2026-05-18
4
+
5
+ ## 1. Resumen
6
+
7
+ Flujos de ejecución de la gema. Generado en **modo completo on-demand** (RFC-007 §2 / dev-enrich 1.3.0): backfill solicitado explícitamente, no incremental. Invariante de honestidad (RFC-001 §3.3): anclado a código real (`file:line`), lógica dispersa marcada como tal, cobertura declarada, **verificado por humano 2026-05-18** contra el código (el LLM extrajo las secuencias; humano confirmó).
8
+
9
+ ## Cobertura (OBLIGATORIO)
10
+
11
+ | Flujo | Estado | Anclaje principal |
12
+ |---|---|---|
13
+ | RPC síncrono | **documentado** | `producer.rb:103-134`, `consumer.rb:152-293` |
14
+ | Fire-and-forget | **documentado** | `producer.rb:47-51,146-161` |
15
+ | Confirmed + basic.return bridge | **documentado** | `producer.rb:72-93,299-333`, `session.rb:204-250` |
16
+ | Consumer subscribe loop + reconnect + health | **documentado** | `consumer.rb:66-127,340-361` |
17
+ | Error handling / RemoteError | **documentado** | `consumer.rb:320-329`, `remote_error.rb`, `raise_error.rb:32-65` |
18
+ | Client middleware stack (onion) | **documentado** | `middleware/stack.rb:43-47`, `base.rb:35-43` |
19
+
20
+ Cobertura completa a `a5cdb10`. Acreta incremental en cada PR que toque un flujo (default RFC-007). Ausencia futura ≠ inexistencia.
21
+
22
+ ## 2. Cuerpo
23
+
24
+ ### Flujo: RPC síncrono
25
+ Request-response bloqueante; el hilo emisor espera en `Concurrent::IVar` correlacionado por `correlation_id` hasta reply o `RequestTimeout`.
26
+
27
+ ```mermaid
28
+ sequenceDiagram
29
+ participant CL as Client
30
+ participant MW as Middleware Stack
31
+ participant P as Producer
32
+ participant BR as RabbitMQ
33
+ participant CO as Consumer
34
+ participant CT as Controller
35
+ CL->>MW: request (delivery_mode=:rpc)
36
+ MW->>P: call (onion → producer.rpc)
37
+ P->>P: ensure_reply_listener! · cid=UUID · reply_to=amq.rabbitmq.reply-to
38
+ P->>P: @pending_requests[cid]=IVar
39
+ P->>BR: publish (type=path, correlation_id, reply_to)
40
+ P-->>P: future.value(timeout) — BLOQUEA
41
+ BR->>CO: deliver
42
+ CO->>CO: consumer middlewares · recognize(method,path)
43
+ CO->>CT: Controller.call(headers, body)
44
+ CT->>CT: before/around/after + action → render
45
+ CT-->>CO: response (o handle_exception → 500)
46
+ CO->>BR: reply a reply_to (correlation_id)
47
+ BR->>P: reply (basic_consume listener)
48
+ P->>P: @pending_requests[cid].set → IVar resuelve
49
+ P->>P: on_rpc_reply · parse_response
50
+ P-->>CL: response hidratada
51
+ Note over P: bloqueo L122 · timeout → RequestTimeout L124
52
+ ```
53
+ Contexto: `client.rb:97-101` → `producer.rb:103-134` (bloqueo L122) → reply listener `producer.rb:405-424` → `consumer.rb:247,272-293`.
54
+
55
+ ### Flujo: Fire-and-forget
56
+ Publica y retorna `{ 'status' => 202 }` sin esperar broker ni consumer.
57
+
58
+ ```mermaid
59
+ sequenceDiagram
60
+ participant CL as Client
61
+ participant MW as Middleware Stack
62
+ participant P as Producer
63
+ participant BR as RabbitMQ
64
+ CL->>MW: publish (delivery_mode=:publish)
65
+ MW->>P: call (onion → producer.fire)
66
+ P->>BR: publish_message (TCP enqueue, sin ACK)
67
+ P-->>CL: { status: 202, body: nil } (inmediato)
68
+ ```
69
+ Contexto: `client.rb:129-134` → `producer.rb:47-51` → `publish_message producer.rb:146-161`.
70
+
71
+ ### Flujo: Confirmed + basic.return bridge
72
+ ACK del broker síncrono; `basic.return` (mandatory unroutable) llega en el reader thread de Bunny y se puentea al hilo de publish vía `@pending_returns` + `Concurrent::Event` con ventana de tolerancia GVL.
73
+
74
+ ```mermaid
75
+ sequenceDiagram
76
+ participant CL as Client
77
+ participant P as Producer
78
+ participant S as Session
79
+ participant BR as RabbitMQ
80
+ participant RT as Bunny reader thread
81
+ CL->>P: publish confirmed:true [mandatory]
82
+ P->>S: register_return_listener(cid) → Event+slot
83
+ P->>BR: publish_message (mandatory)
84
+ P-->>P: wait_for_confirms! — BLOQUEA (IVar si hay timeout)
85
+ BR-->>RT: basic.return (si unroutable)
86
+ RT->>S: handle_broker_return → signal_return_listener
87
+ S->>S: slot[:info]=info · slot[:event].set
88
+ BR-->>P: confirms (ack/nack)
89
+ P->>P: handle_confirm_result — nack → PublishNacked
90
+ P->>P: handle_return_result → event.wait(RETURN_RACE_WINDOW_S=0.05s)
91
+ P->>P: slot[:info] presente → raise_unroutable!
92
+ P-->>CL: PublishUnroutable (o éxito si slot vacío)
93
+ Note over RT,S: on_return corre antes del raise · su excepción se loggea (no propaga)
94
+ ```
95
+ Contexto: `producer.rb:72-93,200-216,281-345`; `session.rb:70-74,204-250`. Branches: ack→ok · nack→`PublishNacked` (`producer.rb:235`) · return→`PublishUnroutable` (`producer.rb:325`) · timeout→`RequestTimeout` (`producer.rb:214-215`).
96
+
97
+ ### Flujo: Consumer subscribe loop + reconnect + health
98
+ Loop bloqueante infinito con backoff exponencial + jitter en error; health check en TimerTask separado, **acoplado flojo** vía estado de session.
99
+
100
+ ```mermaid
101
+ sequenceDiagram
102
+ participant C as Consumer
103
+ participant S as Session
104
+ participant BR as RabbitMQ
105
+ participant H as Health TimerTask
106
+ C->>S: exchange/queue declare · q.bind(rk)
107
+ C->>H: start_health_check (touch file)
108
+ C->>BR: q.subscribe(manual_ack, block:true) — LOOP
109
+ loop por mensaje
110
+ BR->>C: deliver → consumer_middlewares → process_message
111
+ end
112
+ H-->>S: cada interval: queue_declare(passive:true)
113
+ H-->>S: falla → cierra session (fuerza reconnect)
114
+ Note over C: rescue StandardError → attempt++ · backoff min(nri*2^(n-1), max) · sleep · retry (redeclara)
115
+ Note over C: max_reconnect_attempts alcanzado → raise (fatal) · ensure → shutdown
116
+ ```
117
+ Contexto: `consumer.rb:66-127` (retry L106-124), `consumer.rb:340-361` (health). **Honestidad:** health check es thread aparte (TimerTask); no es parte del manejo de error del loop — se acoplan sólo vía cierre de session. Marcado, no fingido como un único flujo.
118
+
119
+ ### Flujo: Error handling / RemoteError
120
+ Excepción no manejada en controller → serializada (clase/mensaje/backtrace[0..25]) → reply 500 → reconstruida client-side por `Middleware::RaiseError`.
121
+
122
+ ```mermaid
123
+ sequenceDiagram
124
+ participant CT as Controller
125
+ participant CO as Consumer
126
+ participant BR as RabbitMQ
127
+ participant RE as Middleware::RaiseError
128
+ participant CL as Caller
129
+ CT->>CT: action raise → rescue → handle_exception
130
+ CT->>CT: rescue_from match? sí→handler · no→500 + RemoteError.serialize
131
+ CT-->>CO: response 500 (bug_bunny_exception)
132
+ CO->>CO: handle_fatal_error → RemoteError.serialize
133
+ CO->>BR: reply 500 a reply_to
134
+ BR->>RE: response (vía reply listener + parse_response)
135
+ RE->>RE: status 500..599 + bug_bunny_exception
136
+ RE-->>CL: raise BugBunny::RemoteError(class,message,backtrace)
137
+ ```
138
+ Contexto: `controller.rb:200-234`, `consumer.rb:320-329`, `remote_error.rb:29-48`, `raise_error.rb:32-65`. **Honestidad:** backtrace truncado a 25 líneas en serialize; si el controller nunca llega a responder, el cliente expira por timeout en vez de recibir el error (no hay path de error garantizado).
139
+
140
+ ### Flujo: Client middleware stack (onion)
141
+ `Stack#build` hace `@middlewares.reverse.inject(final_action)` → el **primer `use` queda como el más externo** (corre `on_request` primero, `on_complete` último). Documentado en `stack.rb:37-39`; sigue la convención Rack/Faraday (primer registrado = capa externa).
142
+
143
+ ```mermaid
144
+ sequenceDiagram
145
+ participant U as Caller
146
+ participant O as MW externo (primer use)
147
+ participant I as MW interno (último use)
148
+ participant P as Producer (final_action)
149
+ U->>O: call(env)
150
+ O->>O: on_request
151
+ O->>I: @app.call(env)
152
+ I->>I: on_request
153
+ I->>P: @app.call(env) → rpc/fire/confirmed
154
+ P-->>I: response
155
+ I->>I: on_complete
156
+ I-->>O: response
157
+ O->>O: on_complete (RaiseError: 4xx/5xx → raise)
158
+ O-->>U: response / excepción
159
+ ```
160
+ Contexto: `middleware/stack.rb:31-47` (build L43-47, `reverse.inject`), `base.rb:35-43` (`call`: on_request → @app.call → on_complete), `client.rb:160-163` (final_action + `@stack.build(final_action).call(req)`). Orden: `use A; use B` → **A más externo** (corre primero), B envuelve al producer. `RaiseError` solo define `on_complete` (sin `on_request`).
161
+
162
+ ## 3. Inferencias
163
+
164
+ | Inferencia | confidence | a verificar (humano) |
165
+ |---|---|---|
166
+ | Secuencias y `file:line` extraídos por el LLM del código a `a5cdb10`; 2ª pasada LLM corrigió 3 discrepancias (flujo middleware invertido, timeout RPC `producer.rb:124`, timeout confirmed `producer.rb:214-215`) | confirmed | **verificado por humano 2026-05-18** (invariante RFC-001 §3.3 satisfecho) |
167
+ | Orden wire `basic.return → basic.ack` garantizado por AMQP; `RETURN_RACE_WINDOW_S` cubre GVL | declared (código) / inferred (garantía AMQP) | confirmar lectura de `producer.rb:299-308` + spec AMQP |
168
+ | Health check acoplado flojo al loop vía cierre de session | inferred | confirmar `consumer.rb:340-361` vs `106-124` |
169
+
170
+ ## 4. Cobertura y fronteras
171
+
172
+ - **Modo:** completo on-demand (RFC-007 §2). Default futuro = incremental por PR; este artefacto se actualiza en el mismo PR que toque cualquier flujo.
173
+ - **Lógica dispersa marcada (no fingida):** health check (thread aparte vía `Concurrent::TimerTask`, acople flojo con el loop sólo vía cierre de session). RFC-007: marcada, no diagramada como flujo limpio falso. (El orden onion del middleware NO es lógica dispersa: es mecanismo limpio y documentado en `stack.rb:37-39`.)
174
+ - **Fuera de alcance:** estructura (operaciones/interfaz/topología) → dev-structure F2 (no implementado). Significado de términos → `glossary.md`. Datos → n/a (sin DB).
175
+ - **Frescura:** PR que renombre/mueva un método citado → reviewer actualiza el diagrama y `file:line` en el mismo PR (RFC-001 §3.3).
176
+ - **Verificación humana:** completada 2026-05-18 (invariante RFC-001 §3.3 satisfecho). Re-verificar en cada PR que toque un flujo citado (frescura).
@@ -0,0 +1,148 @@
1
+ # Glosario — bug_bunny
2
+
3
+ > meta: artefacto `glosario` · RFC-009 (binding opcional, r: §2 materialización no-tabular) · generado dev-enrich (siembra) · anclado a `a5cdb10` · cobertura: parcial, acreta por PR
4
+
5
+ ## 1. Resumen
6
+
7
+ Vocabulario de dominio (DDD ubiquitous language) del bounded context **bug_bunny**: capa de routing RESTful sobre AMQP. Siembra inicial (dev-enrich: glosario SE PUEDE sembrar = migración desde código + conocimiento). **Gema sin capa de datos** (`docs/data/datos.md` = n/a): por RFC-009 §2 el `Binding:` es **opcional** — se cita solo cuando un símbolo público estable *es* el concepto (clase/value object que lo nombra); concepto o patrón sin símbolo propio → `Binding: n/a`. Nunca binding sintético.
8
+
9
+ ## 2. Cuerpo
10
+
11
+ ## Request
12
+ Value object con toda la metadata del mensaje saliente: path, verbo, body, params, headers y opciones AMQP (exchange, routing_key, delivery_mode, persistent, mandatory).
13
+
14
+ **Binding:**
15
+ - `lib/bug_bunny/request.rb` — `BugBunny::Request` (la clase *es* el concepto)
16
+
17
+ ## Delivery Mode
18
+ Modo de entrega de un mensaje en este contexto: `:rpc` (síncrono, bloquea esperando reply), `:publish` (fire-and-forget, retorna 202), `:confirmed` (async con ACK del broker).
19
+
20
+ **Binding:** n/a (modo, no símbolo que sea el concepto; se realiza vía `Client#delivery_mode` / `Producer#{rpc,fire,confirmed}`)
21
+
22
+ ## RPC
23
+ Request-response síncrono sobre la pseudo-cola `amq.rabbitmq.reply-to` (Direct Reply-to). El hilo emisor bloquea en un `Concurrent::IVar` hasta el reply o `RequestTimeout`.
24
+
25
+ **Binding:** n/a (patrón; sin clase que sea "RPC")
26
+
27
+ ## Fire-and-Forget
28
+ Publicación asíncrona sin espera de respuesta; el caller retorna `{ 'status' => 202 }` de inmediato.
29
+
30
+ **Binding:** n/a (patrón; sin clase propia)
31
+
32
+ ## Publisher Confirms
33
+ Confirmación del broker (`basic.ack`) de recepción del mensaje, expuesta como `confirmed: true`. Dos señales asíncronas se convierten en excepciones raise-eables en el hilo de publish: `basic.nack` → `PublishNacked`; `basic.return` (mandatory unroutable) → `PublishUnroutable`.
34
+
35
+ **Binding:** n/a (extensión AMQP; se realiza en `Producer#confirmed`)
36
+
37
+ ## Mandatory
38
+ Flag de `basic.publish` que pide al broker retornar el mensaje (`basic.return`) si no es ruteable a ninguna cola. Inerte sin `confirmed: true`.
39
+
40
+ **Binding:** n/a (flag AMQP)
41
+
42
+ ## Route
43
+ Patrón compilado verbo+path → Controller#acción; extrae params nombrados por regex.
44
+
45
+ **Binding:**
46
+ - `lib/bug_bunny/routing/route.rb` — `BugBunny::Routing::Route`
47
+
48
+ ## RouteSet
49
+ Registro central de rutas + DSL (`resources`, `namespace`, `member`, `collection`) y `recognize`.
50
+
51
+ **Binding:**
52
+ - `lib/bug_bunny/routing/route_set.rb` — `BugBunny::Routing::RouteSet`
53
+
54
+ ## Controller
55
+ Base class tipo Rails que recibe el mensaje deserializado (body+headers) y produce una respuesta HTTP. Soporta `before/around/after_action`, `rescue_from`, `render`.
56
+
57
+ **Binding:**
58
+ - `lib/bug_bunny/controller.rb` — `BugBunny::Controller`
59
+
60
+ ## Consumer
61
+ Worker bloqueante que escucha una cola, deserializa, rutea via `RouteSet` al controller y responde (RPC reply o ack). Incluye health check periódico.
62
+
63
+ **Binding:**
64
+ - `lib/bug_bunny/consumer.rb` — `BugBunny::Consumer`
65
+
66
+ ## Producer
67
+ Publicador de bajo nivel: serialización, resolución de opciones AMQP, correlación RPC, sincronización de Publisher Confirms.
68
+
69
+ **Binding:**
70
+ - `lib/bug_bunny/producer.rb` — `BugBunny::Producer`
71
+
72
+ ## Session
73
+ Wrapper de un canal Bunny con init perezoso, cascada de config (defaults gema → global → request) y resiliencia (double-checked locking, auto-reconnect). Correlaciona `basic.return` cross-thread.
74
+
75
+ **Binding:**
76
+ - `lib/bug_bunny/session.rb` — `BugBunny::Session`
77
+
78
+ ## Client
79
+ API de alto nivel con arquitectura onion-middleware: construye `Request`, ejecuta el stack y delega en `Producer` via connection pool.
80
+
81
+ **Binding:**
82
+ - `lib/bug_bunny/client.rb` — `BugBunny::Client`
83
+
84
+ ## Resource
85
+ ORM tipo ActiveModel: declara microservicios remotos como objetos Ruby con CRUD (`find`, `where`, `create`, `save`, `destroy`) que emiten RPC/async. `.with` da override de contexto thread-local.
86
+
87
+ **Binding:**
88
+ - `lib/bug_bunny/resource.rb` — `BugBunny::Resource`
89
+
90
+ ## Exchange
91
+ Entidad AMQP que rutea mensajes a colas según binding. Declarada con `durable`/`auto_delete` por la cascada de config.
92
+
93
+ **Binding:** n/a (entidad del broker, no de la gema; se realiza en `Session#exchange`)
94
+
95
+ ## Queue
96
+ Entidad AMQP que retiene mensajes hasta que un Consumer se suscribe. Default de la gema desde 4.16: compartida y durable.
97
+
98
+ **Binding:** n/a (entidad del broker; se realiza en `Session#queue`)
99
+
100
+ ## Routing Key
101
+ Clave que el exchange usa para matchear mensaje→binding de cola. Por defecto = `path` del request salvo override explícito.
102
+
103
+ **Binding:** n/a (concepto AMQP; se computa en `Request#final_routing_key`)
104
+
105
+ ## RemoteError
106
+ Error 500 que propaga clase, mensaje y backtrace originales de la excepción del worker remoto al caller RPC.
107
+
108
+ **Binding:**
109
+ - `lib/bug_bunny/remote_error.rb` — `BugBunny::RemoteError`
110
+
111
+ ## PublishNacked
112
+ Excepción cuando el broker rechaza (`basic.nack`) un mensaje en modo `:confirmed`. Opt-out con `config.nack_raise = false`.
113
+
114
+ **Binding:**
115
+ - `lib/bug_bunny/exception.rb` — `BugBunny::PublishNacked`
116
+
117
+ ## PublishUnroutable
118
+ Excepción cuando un mensaje `mandatory: true` en `:confirmed` no es ruteable a ninguna cola (`basic.return`). Opt-out con `config.return_raise = false`.
119
+
120
+ **Binding:**
121
+ - `lib/bug_bunny/exception.rb` — `BugBunny::PublishUnroutable`
122
+
123
+ ## Consumer Middleware
124
+ Cadena transversal que corre antes del dispatch al controller (tracing, auth, logging); recibe `delivery_info`, `properties`, `body`; debe hacer yield.
125
+
126
+ **Binding:**
127
+ - `lib/bug_bunny/consumer_middleware.rb` — `BugBunny::ConsumerMiddleware`
128
+
129
+ ## Observability
130
+ Mixin de logging estructurado `key=value` que implementa OTel semantic conventions for messaging; `safe_log` nunca lanza; filtra claves sensibles a `[FILTERED]`.
131
+
132
+ **Binding:**
133
+ - `lib/bug_bunny/observability.rb` — `BugBunny::Observability`
134
+
135
+ ## 3. Inferencias
136
+
137
+ | Término | Inferencia | confidence | a verificar |
138
+ |---|---|---|---|
139
+ | "bounded context = bug_bunny" | El significado es local a la gema, no global del ecosistema | inferred | humano confirma encuadre DDD |
140
+ | Significado de cada término | Sembrado desde código + glosario ad-hoc migrado del `skill/SKILL.md`; el LLM redactó la prosa | inferred | humano aporta/corrige el significado de negocio (dev-enrich: el LLM infiere menos) |
141
+ | `Binding:` a clase que "es el concepto" | Criterio RFC-009 §2 aplicado por el LLM (qué símbolo *es* el término) | inferred | humano confirma que el símbolo materializa el término y no es sintético |
142
+
143
+ ## 4. Cobertura y fronteras
144
+
145
+ - **Parcial y acreta:** glosario sembrado (RFC-009 §2; dev-enrich "se PUEDE sembrar"). Términos nuevos se agregan en el PR que los introduce. Ausencia ≠ inexistencia.
146
+ - **Binding opcional (RFC-009 §2, gap gema-sin-datos resuelto, RFC-009 §5 / issue ai_knowledge#91):** sin capa de datos el `Binding:` se omite/`n/a`; se cita símbolo solo cuando *es* el concepto.
147
+ - **Frontera (DAMA-DMBOK):** esto es Business Glossary (term-céntrico). El Data Dictionary (`definición` por columna, RFC-002 §2.c) no aplica — sin tablas.
148
+ - **Frescura:** si un PR renombra/elimina una clase con `Binding:`, el reviewer verifica que ningún binding quede colgado (RFC-009 §2).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BugBunny
4
- VERSION = '4.17.0'
4
+ VERSION = '4.17.1'
5
5
  end
data/skill/SKILL.md CHANGED
@@ -1,28 +1,85 @@
1
+ ---
2
+ name: bug_bunny
3
+ description: >-
4
+ Routing RESTful sobre AMQP/RabbitMQ para microservicios Ruby/Rails: RPC
5
+ síncrono, fire-and-forget, Publisher Confirms, Resource ORM, controllers y
6
+ routing DSL tipo Rails. Usar cuando se integra/depura comunicación entre
7
+ servicios via RabbitMQ con la gema bug_bunny.
8
+ triggers:
9
+ - gema `bug_bunny` / módulo `BugBunny`
10
+ - "símbolos `BugBunny::{Client,Consumer,Resource,Controller}`"
11
+ - "excepciones `BugBunny::{PublishUnroutable,PublishNacked,RemoteError}`"
12
+ ---
13
+
1
14
  # BugBunny Expert
2
15
 
3
- Skill de conocimiento completo sobre BugBunny. Consultame para cualquier pregunta sobre integración, arquitectura, API, errores y antipatrones.
16
+ ## Qué es / cuándo usar
4
17
 
5
- ---
18
+ Gema Ruby: capa de routing RESTful sobre AMQP/RabbitMQ. Microservicios se comunican via RabbitMQ con ergonomía tipo Rails (verbos, controllers, rutas, RPC síncrono, fire-and-forget, Publisher Confirms). Usar esta dependencia cuando integrás/depurás comunicación entre servicios con `bug_bunny`: publicar/consumir, ORM remoto, manejo de errores de broker.
19
+
20
+ ## Contrato resumido (piso mínimo)
21
+
22
+ > Resume el contrato de **`bug_bunny` 4.17.0**. Suficiente para el uso típico sin abrir el detalle; el detalle version-locked está en [`../docs/behavior/behavior.md`](../docs/behavior/behavior.md) (6 flujos) y [`../docs/glossary/glossary.md`](../docs/glossary/glossary.md) (símbolos→significado). Antipatrones/API completa: más abajo (embebido interim, ver Cobertura y fronteras).
23
+
24
+ **Símbolos públicos clave**
25
+
26
+ | Símbolo | Uso típico |
27
+ |---|---|
28
+ | `BugBunny::Client` | `client.request(url, method: :get)` (RPC sync) · `client.publish(url, body:)` (fire-and-forget, 202) · `client.publish(url, confirmed: true, mandatory: true)` (publisher confirms) |
29
+ | `BugBunny::Resource` | ORM tipo AR: `self.exchange=` / `self.resource_name=` / `connection_pool=`; `find/where/create/save/destroy` |
30
+ | `BugBunny::Consumer` | `BugBunny::Consumer.subscribe(connection: BugBunny.create_connection, queue_name:, exchange_name:, routing_key:)` (loop bloqueante) |
31
+ | `BugBunny::Controller` | `before/around/after_action`, `rescue_from`, `render status:, json:` |
32
+ | `BugBunny.routes.draw` | `resources :x` · `namespace` · `member`/`collection` |
33
+ | `BugBunny.configure` | `host/port/username/password` · `rpc_timeout` (default 10) · `nack_raise`/`return_raise` (default `true`) · `on_return` |
34
+ | Excepciones | `RemoteError` (500 con backtrace remoto) · `PublishNacked` · `PublishUnroutable` · `RequestTimeout` · `NotFound` · `UnprocessableEntity` |
35
+
36
+ **Uso típico**
37
+
38
+ ```ruby
39
+ # Servidor
40
+ BugBunny.routes.draw { resources :nodes }
41
+ BugBunny::Consumer.subscribe(connection: BugBunny.create_connection,
42
+ queue_name: 'inv_q', exchange_name: 'inventory', routing_key: 'nodes')
43
+
44
+ # Cliente
45
+ client = BugBunny::Client.new(pool: pool)
46
+ client.request('nodes/42', method: :get) # => { 'status' => 200, 'body' => {...} }
47
+ client.publish('events', body: { type: 'x' }) # => { 'status' => 202 }
48
+ ```
49
+
50
+ **Gotchas/breaking críticos**
51
+
52
+ - URL es **posicional**, no kwarg `path:` — `client.publish(**args)` con `path:` → `ArgumentError`.
53
+ - `confirmed: true` ≠ persistente: para sobrevivir restart hace falta `persistent: true` (+ queue `durable`).
54
+ - `confirmed:true + mandatory:true` con `return_raise` (default `true`) → `PublishUnroutable` si no rutea.
55
+ - `BugBunny::Consumer.subscribe` requiere `connection:`. No correr el Consumer en threads de Puma (loop bloqueante).
56
+ - `exchange_options: { durable: true }` debe matchear la declaración del consumer, o `Bunny::PreconditionFailed`.
57
+
58
+ ## Índice de artefactos (fuente de verdad)
59
+
60
+ El detalle vive en `docs/<capa>/` (modelo `dev-*`); esta skill **indexa y resume**, no duplica. Links relativos = version-locked (mismo tag del release; `gemspec.files` incluye `docs/**`).
61
+
62
+ | Capa | Artefacto | Estado |
63
+ |---|---|---|
64
+ | Glosario de dominio | [docs/glossary/glossary.md](../docs/glossary/glossary.md) | parcial, acreta por PR |
65
+ | Comportamiento (flujos) | [docs/behavior/behavior.md](../docs/behavior/behavior.md) | completa — 6 flujos |
66
+ | Datos | — | n/a — gema sin DB |
67
+ | Operaciones / Interfaz / Topología | — | F2 no implementado — ver Cobertura y fronteras |
68
+
69
+ > **Glosario:** migrado a [docs/glossary/glossary.md](../docs/glossary/glossary.md)
70
+ > (RFC-008 §2 — el compuesto referencia, no copia). Términos AMQP base
71
+ > (Exchange, Queue, Routing Key, RPC, Publisher Confirms, Mandatory, etc.) y su
72
+ > binding físico están ahí.
73
+
74
+ ## Cobertura y fronteras
75
+
76
+ **Coexistencia transitoria con destino pendiente (RFC-008 §2 — interim de migración):** mientras la capa de detalle destino (operaciones/interfaz/topología) esté declarada pero **no implementada** (dev-structure F1, F2 del plan), permanecen embebidos bajo el interim normado:
77
+
78
+ - **En esta skill (abajo):** el contrato detallado (jerarquía de excepciones, API de config, modos de entrega) **y** el diagrama de arquitectura (flujo RPC). El *Contrato resumido* de arriba es el piso mínimo (RFC-008 §2); lo de abajo es el detalle interim hasta que exista `docs/api|interface|topology`.
79
+ - **En `README.md`:** el contrato (sin el diagrama de arquitectura).
80
+ - **Guías how-to** (`references/*.md`, pre-estándar): destino futuro `docs/howto/`.
6
81
 
7
- ## Glosario
8
-
9
- **AMQP** — Advanced Message Queuing Protocol. Protocolo binario que implementa RabbitMQ.
10
- **Bunny** — Cliente Ruby para AMQP. BugBunny lo usa internamente para conexiones, canales y publicación.
11
- **Exchange** — Recibe mensajes del producer y los enruta a queues según reglas. Tipos: `direct` (match exacto), `topic` (wildcards), `fanout` (broadcast).
12
- **Queue** — Almacena mensajes hasta que un consumer los consume. Las queues durables sobreviven reinicios del broker.
13
- **Routing Key** — String que el producer adjunta al mensaje. El exchange lo usa para decidir a qué queues enrutar.
14
- **Binding** — Enlace entre un exchange y una queue, opcionalmente con un patrón de routing key.
15
- **Session** — `BugBunny::Session` envuelve canales de Bunny con thread-safety y double-checked locking.
16
- **RPC** — Patrón síncrono que usa la pseudo-cola `amq.rabbitmq.reply-to` para respuestas sin crear queues temporales.
17
- **Fire-and-Forget** — Patrón asíncrono donde el producer publica y continúa sin esperar respuesta. Retorna `{ 'status' => 202 }`.
18
- **Publisher Confirms** — Extensión de RabbitMQ que confirma al publisher que el broker recibió el mensaje. BugBunny lo expone como `confirmed: true` en `Client#publish`: el publish bloquea hasta `wait_for_confirms`. Dos señales asíncronas del broker, ambas convertidas en excepciones raise-eables en el publish thread:
19
- - **NACK** (rechazo explícito) → `BugBunny::PublishNacked` (configurable via `config.nack_raise = false`).
20
- - **basic.return** (mandatory unroutable) → `BugBunny::PublishUnroutable` (configurable via `config.return_raise = false`).
21
- **Mandatory** — Flag de `basic.publish` que pide al broker retornar el mensaje al publisher si no es ruteable a ninguna cola. Se procesa via `basic.return` (no es respuesta del request).
22
- **basic.return** — Evento asincrónico que Bunny dispatcha por exchange (`Bunny::Exchange#on_return`). BugBunny registra un handler único por nombre de exchange al resolverlo en `Session#exchange` y lo delega a `Configuration#on_return` o al default que logea.
23
- **Resource** — ORM tipo ActiveRecord que mapea operaciones CRUD a llamadas AMQP.
24
- **Consumer** — Worker bloqueante que despacha mensajes a controladores mediante un Router.
25
- **Connection Pool** — Pool de conexiones (`connection_pool` gem) que comparte sessions entre threads. Cada slot cachea su `Session` y `Producer`.
82
+ Por RFC-008 §2: no se fabrica la capa, no se borra contrato sin destino, no se duplica; migra cuando F2 entregue, mismo PR. Estado transitorio declarado, no excepción permanente. Origen del gap (resuelto, normado): [sequre/ai_knowledge#95](https://github.com/sequre/ai_knowledge/issues/95).
26
83
 
27
84
  ---
28
85
 
data/skills.yml CHANGED
@@ -8,7 +8,17 @@ skills:
8
8
  repo: sequre/ai_knowledge
9
9
  gem-release:
10
10
  repo: sequre/ai_knowledge
11
- skill-builder:
11
+ dev-structure:
12
+ repo: sequre/ai_knowledge
13
+ dev-compose:
14
+ repo: sequre/ai_knowledge
15
+ dev-enrich:
16
+ repo: sequre/ai_knowledge
17
+ skill-feedback:
18
+ repo: sequre/ai_knowledge
19
+ agent-issue:
20
+ repo: sequre/ai_knowledge
21
+ dev-flow:
12
22
  repo: sequre/ai_knowledge
13
23
  matrix-element:
14
24
  repo: sequre/ai_knowledge
@@ -17,10 +27,6 @@ skills:
17
27
  auth_token: "${MATRIX_AUTH_TOKEN}"
18
28
  rooms:
19
29
  agents: "!VCHwQXgmXdyhhhPhoz:matrix.cloud.wispro.co"
20
- skill-feedback:
21
- repo: sequre/ai_knowledge
22
- agent-issue:
23
- repo: sequre/ai_knowledge
24
30
  documentation-writer:
25
31
  repo: github/awesome-copilot
26
32
  path: skills/documentation-writer
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bug_bunny
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.17.0
4
+ version: 4.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - gabix
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-13 00:00:00.000000000 Z
11
+ date: 2026-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bunny
@@ -233,6 +233,8 @@ files:
233
233
  - CLAUDE.md
234
234
  - README.md
235
235
  - Rakefile
236
+ - docs/behavior/behavior.md
237
+ - docs/glossary/glossary.md
236
238
  - initializer_example.rb
237
239
  - lib/bug_bunny.rb
238
240
  - lib/bug_bunny/client.rb
@@ -304,7 +306,7 @@ metadata:
304
306
  homepage_uri: https://github.com/gedera/bug_bunny
305
307
  source_code_uri: https://github.com/gedera/bug_bunny
306
308
  changelog_uri: https://github.com/gedera/bug_bunny/blob/main/CHANGELOG.md
307
- documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.17.0/skill
309
+ documentation_uri: https://github.com/gedera/bug_bunny/blob/v4.17.1/skill
308
310
  post_install_message:
309
311
  rdoc_options: []
310
312
  require_paths: