data_drain 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48ceb077ad9f22d8550ef1e1974faf7ae77fc9fd2551b26343b067bb50ca36da
4
- data.tar.gz: 1fee979b853e79384be9f18b4031c4b5a5cb4a3519a3e95da1824d5546d60283
3
+ metadata.gz: 8fc6bdf2a5a21825f9bc07185c0fc259056646c6084de2a42fd2d4e3198af4f2
4
+ data.tar.gz: aa7623b2ec910da4491c38904aa4623fd48a466ccc97a38124f29aafd9677655
5
5
  SHA512:
6
- metadata.gz: 216ed91eaed0d850f4c882a87a6f9c689ad4f934b60e005967a8236d8d68daecdbf3b7ccc066875e93bdb2ae371db375166fae238069e56d7936ff1a341eeb91
7
- data.tar.gz: 46595a513206b4966d58e4a42745ba1c86ba89f7cab21d3f1847446b87a1d32e8cdd5159a2dc6de58f5a579c9cc0446ed4fbc9c3702656d4309053451180d07e
6
+ metadata.gz: '0201838e917434b529250689a5987008f9e8895366b56a78522752fe5b490fb6ab3d0b4c40dc81bcc830af3132c005fc8d136ab719093a16f0c9de5221e3421c'
7
+ data.tar.gz: 5be0c9c4a47d07f479f49c977d0490aad909a33fbc0c9292594bd51163132ee5229e0131f23d253120732d530930e5474bd31abbef7a5289f3022055799b5525
data/CHANGELOG.md CHANGED
@@ -1,9 +1,54 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-04-15
4
+
5
+ ### Refactor
6
+ - `Engine#call` refactorizado: extraídos `step_count`, `step_export`, `step_verify`, `step_purge` como métodos privados con `timed` helper. CC bajó de 13 a 5. Eventos emitidos idénticos al comportamiento anterior. (item 10)
7
+ - Extraído `DataDrain::Observability::Timing` mixin compartido entre Engine y FileIngestor. (item 20)
8
+ - `FileIngestor#call` refactorizado análogo a Engine. (item 20)
9
+ - Eliminados todos los `# rubocop:disable Metrics/*` en `lib/`. (item 20)
10
+
11
+ ### Features
12
+ - `config.vacuum_after_purge = false` (default). Si `true`, ejecuta `VACUUM ANALYZE` post-purga cuando hubo deletes. Emite `engine.vacuum_complete` con dead_tuples antes/después y duración. Errores PG se capturan como `engine.vacuum_error` WARN. (item 5)
13
+ - `config.slow_batch_threshold_s = 30` y `config.slow_batch_alert_after = 5`. Detecta lotes de purga lentos. Emite `engine.slow_batch` WARN por cada lote lento, `engine.purge_degraded` WARN una vez por streak. Incluye hint a docs de tuning. (item 11b)
14
+
15
+ ### Security
16
+ - `Record.connection` aplica `SET lock_configuration=true` post-setup. Congela cualquier SET futuro sobre la conexión (defensa en profundidad). NO afecta secrets ni extensiones ya cargadas. (item 6)
17
+
18
+ ### Telemetry nueva
19
+ - `engine.vacuum_complete`, `engine.vacuum_error`, `engine.slow_batch`, `engine.purge_degraded`.
20
+
21
+ ### Tests
22
+ - Coverage se mantiene ≥ 80%.
23
+ - Nuevo test de equivalencia para Engine (eventos idénticos pre/post refactor).
24
+ - Timecop agregado para tests de timing (item 11b).
25
+
26
+ ## [0.2.2] - 2026-04-14
27
+
28
+ ### Security
29
+ - `Observability#safe_log` filtra secretos con regex `/password|passwd|pass|secret|token|api_key|apikey|auth|credential|private_key/i` — ahora captura variantes como `db_password`, `aws_secret_access_key`, `bearer_token`, `private_key`, `*credential*`. (item 9)
30
+
31
+ ### Features
32
+ - `GlueRunner.run_and_wait` acepta `max_wait_seconds:` para evitar bloqueo indefinido. Default `nil` (sin límite, backward-compatible). Emite `glue_runner.timeout` y levanta `DataDrain::Error` cuando excede. (item 7)
33
+ - `Configuration#validate!` y `Configuration#validate_for_engine!` invocados automáticamente en `Engine`, `FileIngestor` y `GlueRunner`. Falla rápido con errores descriptivos si falta configuración. (item 8)
34
+
35
+ ### Docs
36
+ - `skill/references/postgres-tuning.md`: guía de tuning Postgres por tamaño de tabla — índices, VACUUM post-purga, particionamiento, diagnóstico. (item 11a)
37
+
38
+ ### Cleanups (review PR #6)
39
+ - Fix typo `依赖` en CHANGELOG v0.2.1 (A1).
40
+ - Comment explicativo en `Record.disconnect!` rescue (A2).
41
+ - Cobertura real string-keys vs symbol-keys en `Record.build_query_path` (A3).
42
+ - Cerrar conn+db en `record_spec.rb#before(:all)` para evitar memory leak en suite (A4).
43
+ - Reorder `public`/`private` en `storage/s3.rb` (B1).
44
+
45
+ ### BREAKING (preventivo)
46
+ - `Engine.new` / `FileIngestor.new` / `GlueRunner.run_and_wait` ahora levantan `DataDrain::ConfigurationError` en el boot si la configuración está incompleta. Antes fallaban tarde con errores oscuros. La gema aún no está en uso en producción — no hay impacto real.
47
+
3
48
  ## [0.2.1] - 2026-04-13
4
49
 
5
50
  ### Correcciones
6
- - CI: Descarga binario pre-compilado de DuckDB en vez de依赖 del sistema (`libduckdb-dev`). Soporta Ruby 3.4.4 en GitHub Actions.
51
+ - CI: Descarga binario pre-compilado de DuckDB en vez de depender del sistema (`libduckdb-dev`). Soporta Ruby 3.4.4 en GitHub Actions.
7
52
  - CI: Opt-in a Node.js 24 (`FORCE_JAVASCRIPT_ACTIONS_TO_NODE24`).
8
53
  - CI: Ejecuta solo specs en CI (RuboCop vía local) para evitar 48 ofensas pre-existentes en specs.
9
54
  - PR feedback: Test `aws_region` con comillas, `minimum_coverage` 80%, antipatrón 12 actualizado.
data/CLAUDE.md CHANGED
@@ -59,7 +59,7 @@ La telemetría debe ser estructurada (KV) para ser procesada por `exis_ray`.
59
59
  - **Automatización:** El campo `source` lo inyecta automáticamente `exis_ray` — no incluirlo manualmente.
60
60
  - **DEBUG:** Siempre en forma de bloque: `logger.debug { "k=#{v}" }`.
61
61
  - **Duraciones:** Usar siempre `Process.clock_gettime(Process::CLOCK_MONOTONIC)`.
62
- - **Sensibilidad:** Filtrar datos sensibles (`password`, `token`, `secret`) → `[FILTERED]`.
62
+ - **Sensibilidad:** `Observability#safe_log` filtra claves con regex `/password|passwd|pass|secret|token|api_key|apikey|auth|credential|private_key/i` → `[FILTERED]`.
63
63
 
64
64
  ## Código Ruby
65
65
 
@@ -79,3 +79,5 @@ bin/console # REPL de desarrollo
79
79
 
80
80
  - `limit_ram` y `tmp_directory` en la configuración evitan OOM en contenedores
81
81
  - DuckDB usa spill-to-disk automáticamente cuando `tmp_directory` está seteado
82
+ - Ver `skill/references/postgres-tuning.md` para guía de tuning por tamaño de tabla:
83
+ índices, VACUUM post-purga, y particionamiento declarativo para tablas >100GB
data/README.md CHANGED
@@ -51,6 +51,9 @@ DataDrain.configure do |config|
51
51
  config.limit_ram = '2GB' # evita OOM en contenedores
52
52
  config.tmp_directory = '/tmp/duckdb_work' # spill-to-disk (preferir SSD/NVMe)
53
53
 
54
+ # Tuning de Postgres: ver skill/references/postgres-tuning.md
55
+ # Índices para DELETE, VACUUM post-purga, particionamiento para tablas >100GB
56
+
54
57
  config.logger = Rails.logger
55
58
  end
56
59
  ```
@@ -1,7 +1,7 @@
1
1
  # DataDrain — Plan de Mejora v0.2.0 → v0.3.1
2
2
 
3
- **Versión actual:** 0.2.0
4
- **Última actualización:** 2026-04-13
3
+ **Versión actual:** 0.2.2
4
+ **Última actualización:** 2026-04-14
5
5
  **Owner:** Gabriel
6
6
  **Estado global:** No iniciado
7
7
 
@@ -32,7 +32,7 @@ DataDrain v0.1.19 es una gema bien arquitecturada (Storage Adapter, Observabilit
32
32
  - **Memory leak potencial:** conexión DuckDB thread-local sin cleanup.
33
33
  - **Documentación de tuning ausente:** sin guía para purgas masivas, índices, particionamiento.
34
34
 
35
- Este plan agrupa 17 items en 4 releases incrementales (v0.2.0 → v0.3.1) priorizados por impacto.
35
+ Este plan agrupa 24 items (17 originales + 7 surgidos post v0.2.1) en 5+ releases incrementales (v0.2.0 → v0.3.1) priorizados por impacto.
36
36
 
37
37
  ---
38
38
 
@@ -433,12 +433,13 @@ spec/
433
433
 
434
434
  #### Item 5 — VACUUM ANALYZE opcional post-purga
435
435
 
436
- **Estado:** `[ ]`
436
+ **Estado:** `[x]`
437
437
  **Prioridad:** P1
438
438
  **Tipo:** `feat` `perf`
439
439
  **Compatibilidad:** backward-compatible (default `false`, opt-in)
440
440
  **Estimación:** S (2-3h)
441
441
  **Release:** v0.2.1
442
+ **Commit:** `93bf8a8`
442
443
 
443
444
  ##### Contexto
444
445
 
@@ -502,7 +503,7 @@ Purgar millones de rows deja dead tuples en Postgres. Sin `VACUUM`, el espacio n
502
503
 
503
504
  #### Item 7 — `max_wait_seconds` en `GlueRunner.run_and_wait`
504
505
 
505
- **Estado:** `[ ]`
506
+ **Estado:** `[x]`
506
507
  **Prioridad:** P1
507
508
  **Tipo:** `feat`
508
509
  **Compatibilidad:** backward-compatible
@@ -553,7 +554,7 @@ Purgar millones de rows deja dead tuples en Postgres. Sin `VACUUM`, el espacio n
553
554
 
554
555
  #### Item 8 — `Configuration#validate!`
555
556
 
556
- **Estado:** `[ ]`
557
+ **Estado:** `[x]`
557
558
  **Prioridad:** P1
558
559
  **Tipo:** `feat`
559
560
  **Compatibilidad:** backward-compatible (validación opcional explícita)
@@ -624,7 +625,7 @@ Purgar millones de rows deja dead tuples en Postgres. Sin `VACUUM`, el espacio n
624
625
 
625
626
  #### Item 9 — Filtro de secretos por regex en Observability
626
627
 
627
- **Estado:** `[ ]`
628
+ **Estado:** `[x]`
628
629
  **Prioridad:** P1
629
630
  **Tipo:** `security`
630
631
  **Compatibilidad:** backward-compatible (filtra más, no menos)
@@ -670,7 +671,7 @@ val = SENSITIVE_KEY_PATTERN.match?(k.to_s) ? "[FILTERED]" : v
670
671
 
671
672
  #### Item 11a — Documentación de Postgres tuning por tamaño de tabla
672
673
 
673
- **Estado:** `[ ]`
674
+ **Estado:** `[x]`
674
675
  **Prioridad:** P1
675
676
  **Tipo:** `docs`
676
677
  **Compatibilidad:** N/A
@@ -754,12 +755,13 @@ Contenido:
754
755
 
755
756
  #### Item 6 — Sandboxing de `Record.connection`
756
757
 
757
- **Estado:** `[ ]`
758
+ **Estado:** `[x]`
758
759
  **Prioridad:** P1
759
760
  **Tipo:** `security`
760
761
  **Compatibilidad:** backward-compatible (con risk de breaking si caller hizo workarounds raros)
761
762
  **Estimación:** M (3-4h)
762
763
  **Release:** v0.3.0
764
+ **Commit:** `f042c56`
763
765
 
764
766
  ##### Contexto
765
767
 
@@ -800,12 +802,13 @@ Reduce blast radius si alguien intenta inyectar SQL malicioso vía `where_clause
800
802
 
801
803
  #### Item 10 — Refactor `Engine#call` (CC=13 → ~5)
802
804
 
803
- **Estado:** `[ ]`
805
+ **Estado:** `[x]`
804
806
  **Prioridad:** P1
805
807
  **Tipo:** `refactor`
806
808
  **Compatibilidad:** backward-compatible
807
809
  **Estimación:** M (4-6h)
808
810
  **Release:** v0.3.0
811
+ **Commit:** `6a06850`
809
812
 
810
813
  ##### Contexto
811
814
 
@@ -878,12 +881,13 @@ Reduce blast radius si alguien intenta inyectar SQL malicioso vía `where_clause
878
881
 
879
882
  #### Item 11b — Warning runtime de purga lenta sin avance
880
883
 
881
- **Estado:** `[ ]`
884
+ **Estado:** `[x]`
882
885
  **Prioridad:** P1
883
886
  **Tipo:** `feat` `perf`
884
887
  **Compatibilidad:** backward-compatible
885
888
  **Estimación:** M (3-4h)
886
889
  **Release:** v0.3.0
890
+ **Commit:** `d72ec0a`
887
891
 
888
892
  ##### Contexto
889
893
 
@@ -1091,6 +1095,262 @@ Solo donde sea limpio. No forzar.
1091
1095
 
1092
1096
  ---
1093
1097
 
1098
+ ### Items nuevos — surgidos post v0.2.1 (no en plan original)
1099
+
1100
+ Agregados el 2026-04-14 tras review de v0.2.1 y análisis del workaround de CI.
1101
+
1102
+ ---
1103
+
1104
+ #### Item 17 — Arreglar 48 ofensas RuboCop en `spec/` y re-habilitar en CI
1105
+
1106
+ **Estado:** `[ ]`
1107
+ **Prioridad:** P2
1108
+ **Tipo:** `chore` `test`
1109
+ **Compatibilidad:** N/A
1110
+ **Estimación:** M (3-5h)
1111
+ **Release sugerido:** v0.3.1
1112
+
1113
+ ##### Contexto
1114
+
1115
+ v0.2.1 excluyó `spec/` de RuboCop en `.rubocop.yml` para desbloquear el CI. La exclusión oculta ~48 ofensas preexistentes. El item 14 del roadmap ("CI con GitHub Actions") asume que RuboCop corre en CI — hoy no aplica a specs.
1116
+
1117
+ ##### Cambios
1118
+
1119
+ 1. Quitar `Exclude: - spec/` de `.rubocop.yml`.
1120
+ 2. `bundle exec rubocop spec/ --auto-correct` para fixes automáticos.
1121
+ 3. Fix manual de ofensas no auto-corregibles (típicamente `Metrics/BlockLength`, `RSpec/MultipleExpectations`, etc.).
1122
+ 4. Re-habilitar rubocop en el workflow CI (`.github/workflows/main.yml`).
1123
+
1124
+ ##### Criterios de aceptación
1125
+
1126
+ - [ ] `bundle exec rubocop` (sin argumentos, todo el proyecto) sin ofensas.
1127
+ - [ ] Workflow CI corre `bundle exec rubocop` antes de `rspec`.
1128
+ - [ ] Todos los specs siguen pasando después del auto-correct.
1129
+
1130
+ ##### Riesgos
1131
+
1132
+ - **Auto-correct puede romper tests.** Correr `rspec` tras cada batch de auto-correct.
1133
+ - Algunas ofensas requieren configurar `RSpec/*` cops con límites razonables (ej. `Max: 20` para `RSpec/ExampleLength`).
1134
+
1135
+ ---
1136
+
1137
+ #### Item 18 — Matrix Ruby en CI (3.2, 3.3, 3.4)
1138
+
1139
+ **Estado:** `[ ]`
1140
+ **Prioridad:** P2
1141
+ **Tipo:** `chore`
1142
+ **Compatibilidad:** N/A
1143
+ **Estimación:** S (1-2h)
1144
+ **Release sugerido:** v0.3.1
1145
+
1146
+ ##### Contexto
1147
+
1148
+ v0.2.1 solo corre CI en Ruby 3.4.4. La gema declara `required_ruby_version = ">= 3.0.0"`. Para validar compatibilidad con Rubies soportadas por Rails (3.2, 3.3), agregar matrix.
1149
+
1150
+ ##### Cambios
1151
+
1152
+ 1. En `.github/workflows/main.yml`:
1153
+ ```yaml
1154
+ strategy:
1155
+ matrix:
1156
+ ruby: ["3.2", "3.3", "3.4"]
1157
+ ```
1158
+ 2. Verificar que el binario pre-compilado de DuckDB 1.4.4 funciona en las 3 versiones de Ruby (debería — C ABI estable).
1159
+ 3. Ajustar `TargetRubyVersion` en `.rubocop.yml` si corresponde (hoy `3.2`, puede bajarse a `3.0` si la gema lo soporta realmente — verificar `required_ruby_version`).
1160
+
1161
+ ##### Criterios de aceptación
1162
+
1163
+ - [ ] Matrix con 3 versiones Ruby corre en CI.
1164
+ - [ ] Tiempo total < 10 min.
1165
+ - [ ] Las 3 versiones pasan specs.
1166
+
1167
+ ##### Riesgos
1168
+
1169
+ - **DuckDB prebuilt binary dep** — si la C extension no linkea bien en alguna versión, tendrá que recompilar (cambia tiempo de CI).
1170
+
1171
+ ---
1172
+
1173
+ #### Item 19 — Migrar tests S3 de `stub_const` a `Aws::S3::Client.stub_responses`
1174
+
1175
+ **Estado:** `[ ]`
1176
+ **Prioridad:** P2
1177
+ **Tipo:** `refactor` `test`
1178
+ **Compatibilidad:** N/A
1179
+ **Estimación:** S (2h)
1180
+ **Release sugerido:** v0.3.1
1181
+
1182
+ ##### Contexto
1183
+
1184
+ `spec/data_drain/storage/s3_spec.rb` usa `stub_const("Aws::S3::Client", Class.new ...)` para mockear el cliente AWS. Esto reemplaza la clase entera y es frágil (si AWS SDK agrega métodos nuevos, los tests no los cubren). AWS SDK soporta nativamente `Aws::S3::Client.new(stub_responses: true)`.
1185
+
1186
+ ##### Cambios
1187
+
1188
+ 1. En `spec/data_drain/storage/s3_spec.rb`, reemplazar `stub_const` por:
1189
+ ```ruby
1190
+ let(:s3_client) { Aws::S3::Client.new(stub_responses: true) }
1191
+
1192
+ before do
1193
+ s3_client.stub_responses(:list_objects_v2, contents: [...])
1194
+ allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
1195
+ end
1196
+ ```
1197
+ 2. Los tests de `destroy_partitions` quedan más declarativos.
1198
+
1199
+ ##### Criterios de aceptación
1200
+
1201
+ - [ ] Tests pasan con el SDK real (`stub_responses` usa el client real, no mock).
1202
+ - [ ] No más `stub_const("Aws::S3::Client", ...)`.
1203
+ - [ ] Cobertura igual o superior.
1204
+
1205
+ ##### Riesgos
1206
+
1207
+ - **Setup diferente:** `stub_responses` requiere configurar cada API call. Más verboso pero más robusto.
1208
+
1209
+ ---
1210
+
1211
+ #### Item 20 — Limpiar `rubocop:disable` en `lib/` agregados en v0.2.0
1212
+
1213
+ **Estado:** `[x]`
1214
+ **Prioridad:** P2
1215
+ **Tipo:** `refactor`
1216
+ **Compatibilidad:** N/A
1217
+ **Estimación:** Depende del item 10 (refactor Engine#call)
1218
+ **Release sugerido:** v0.3.0 (junto con item 10)
1219
+ **Commit:** `f6f4ddc` (FileIngestor), `02d207c` (S3 refactor), `5522c79` (Timing mixin)
1220
+
1221
+ ##### Contexto
1222
+
1223
+ v0.2.0 agregó `# rubocop:disable` extensivos en 4 archivos:
1224
+ - `lib/data_drain/engine.rb`: `Metrics/ClassLength, AbcSize, MethodLength, Naming/AccessorMethodName`
1225
+ - `lib/data_drain/file_ingestor.rb`: `Metrics/AbcSize, CyclomaticComplexity, PerceivedComplexity, MethodLength`
1226
+ - `lib/data_drain/record.rb`: `Metrics/AbcSize, MethodLength`
1227
+ - `lib/data_drain/storage/s3.rb`: `Metrics/AbcSize, CyclomaticComplexity, MethodLength`
1228
+
1229
+ Workaround razonable durante v0.2.0 pero deja deuda visible.
1230
+
1231
+ ##### Cambios
1232
+
1233
+ Item 10 del roadmap (refactor `Engine#call` CC=13→5) resolverá parte. Este item complementa:
1234
+
1235
+ 1. Refactor `FileIngestor#call` en métodos privados (similar a item 10 para Engine).
1236
+ 2. Refactor `Storage::S3#setup_duckdb` + `#create_s3_secret` — el CC viene del branching credencial explícita vs `credential_chain`. Puede simplificarse.
1237
+ 3. Refactor `Record#execute_and_instantiate` — probablemente un guard clause resuelve.
1238
+ 4. Quitar todos los `rubocop:disable` a medida que se refactoriza.
1239
+
1240
+ ##### Criterios de aceptación
1241
+
1242
+ - [ ] Ningún `# rubocop:disable Metrics/*` en `lib/`.
1243
+ - [ ] `bundle exec rubocop lib/` sin ofensas ni disables.
1244
+ - [ ] Tests verdes.
1245
+
1246
+ ##### Riesgos
1247
+
1248
+ - **Refactor mayor de `FileIngestor`** — no tiene tests integration tan robustos como Engine. Requiere atención.
1249
+
1250
+ ---
1251
+
1252
+ #### Item 22 — Cache de RuboCop en CI
1253
+
1254
+ **Estado:** `[ ]`
1255
+ **Prioridad:** P3
1256
+ **Tipo:** `chore`
1257
+ **Compatibilidad:** N/A
1258
+ **Estimación:** XS (30min)
1259
+ **Release sugerido:** v0.3.1
1260
+ **Origen:** Review big-pickle 2026-04-14 (ClickUp 86b9dka0c).
1261
+
1262
+ ##### Contexto
1263
+
1264
+ Cuando el item 17 re-habilite rubocop en CI, correrá en cada push. Cachear el resultado con `actions/cache` sobre `~/.cache/rubocop_cache` acelera runs subsecuentes de 30s a <5s.
1265
+
1266
+ ##### Cambios
1267
+
1268
+ En `.github/workflows/main.yml`, agregar step antes de `rubocop`:
1269
+ ```yaml
1270
+ - uses: actions/cache@v4
1271
+ with:
1272
+ path: ~/.cache/rubocop_cache
1273
+ key: rubocop-${{ matrix.ruby }}-${{ hashFiles('.rubocop.yml') }}
1274
+ restore-keys: rubocop-${{ matrix.ruby }}-
1275
+ ```
1276
+
1277
+ ##### Criterios de aceptación
1278
+
1279
+ - [ ] Runs subsecuentes (sin cambios en `.rubocop.yml`) bajan tiempo de rubocop step.
1280
+ - [ ] Cache invalida correctamente si `.rubocop.yml` cambia.
1281
+
1282
+ ##### Riesgos
1283
+
1284
+ - Ninguno significativo.
1285
+
1286
+ ---
1287
+
1288
+ #### Item 23 — Coverage ≥ 90% en SimpleCov
1289
+
1290
+ **Estado:** `[ ]`
1291
+ **Prioridad:** P2
1292
+ **Tipo:** `test`
1293
+ **Compatibilidad:** N/A
1294
+ **Estimación:** M (4-6h)
1295
+ **Release sugerido:** v0.3.1
1296
+ **Origen:** Review big-pickle 2026-04-14.
1297
+
1298
+ ##### Contexto
1299
+
1300
+ v0.2.0 dejó `minimum_coverage 80` con cobertura real ~97%. Subir el umbral a 90% previene regresiones futuras y obliga a cubrir ramas nuevas.
1301
+
1302
+ ##### Cambios
1303
+
1304
+ 1. `spec/spec_helper.rb`: cambiar `minimum_coverage 80` → `minimum_coverage 90`.
1305
+ 2. Correr `bundle exec rspec` y verificar qué ramas quedan descubiertas.
1306
+ 3. Agregar tests para cubrirlas (típicamente branches de error, edge cases de `safe_log` y `destroy_partitions`).
1307
+
1308
+ ##### Criterios de aceptación
1309
+
1310
+ - [ ] `SimpleCov` reporta ≥ 90% líneas.
1311
+ - [ ] Ningún archivo de `lib/` con cobertura < 80%.
1312
+ - [ ] CI falla si alguna corrida baja del 90%.
1313
+
1314
+ ##### Riesgos
1315
+
1316
+ - Puede requerir tests con Postgres real para cubrir `purge_from_postgres` completo — decidir si entran en el suite default o bajo tag `:integration`.
1317
+
1318
+ ---
1319
+
1320
+ #### Item 24 — CI badge en README
1321
+
1322
+ **Estado:** `[ ]`
1323
+ **Prioridad:** P3
1324
+ **Tipo:** `docs`
1325
+ **Compatibilidad:** N/A
1326
+ **Estimación:** XS (10min)
1327
+ **Release sugerido:** v0.3.1
1328
+ **Origen:** Review big-pickle 2026-04-14.
1329
+
1330
+ ##### Contexto
1331
+
1332
+ README no muestra estado de CI. Un badge al inicio confirma visualmente que la gema es activamente testeada.
1333
+
1334
+ ##### Cambios
1335
+
1336
+ Agregar al tope del README después del título:
1337
+ ```markdown
1338
+ # DataDrain
1339
+
1340
+ [![CI](https://github.com/gedera/data_drain/actions/workflows/main.yml/badge.svg)](https://github.com/gedera/data_drain/actions/workflows/main.yml)
1341
+ ```
1342
+
1343
+ Opcionalmente badges adicionales:
1344
+ - Cobertura (requiere integrar con CodeClimate o Codecov)
1345
+ - Version de Gem (si se publica en RubyGems)
1346
+
1347
+ ##### Criterios de aceptación
1348
+
1349
+ - [ ] Badge se ve verde en main.
1350
+ - [ ] Badge apunta al workflow correcto.
1351
+
1352
+ ---
1353
+
1094
1354
  ## Riesgos transversales
1095
1355
 
1096
1356
  ### Coordinación con Gemini