chronos-ruby 0.9.0.pre.4 → 1.1.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/README.md +108 -197
  4. data/contracts/apm-batch-v1.schema.json +51 -1
  5. data/docs/adr/ADR-007-feature-detection.md +25 -0
  6. data/docs/adr/ADR-008-context-store.md +25 -0
  7. data/docs/adr/ADR-009-sampling.md +25 -0
  8. data/docs/adr/ADR-010-opentelemetry-interoperability.md +25 -0
  9. data/docs/adr/ADR-015-bounded-apm-aggregation.md +3 -3
  10. data/docs/adr/ADR-018-pre-1.0-hardening.md +6 -2
  11. data/docs/adr/ADR-019-bounded-query-diagnostics.md +44 -0
  12. data/docs/architecture.md +5 -2
  13. data/docs/compatibility.md +30 -23
  14. data/docs/configuration.md +21 -0
  15. data/docs/data-collected.md +9 -3
  16. data/docs/deprecation-policy.md +1 -1
  17. data/docs/examples/plain-ruby.md +6 -0
  18. data/docs/migration-from-airbrake.md +1 -1
  19. data/docs/modules/apm-aggregation.md +18 -5
  20. data/docs/modules/breadcrumbs.md +23 -0
  21. data/docs/modules/context.md +21 -0
  22. data/docs/modules/deploys.md +23 -0
  23. data/docs/modules/job-monitoring.md +22 -0
  24. data/docs/modules/request-monitoring.md +20 -0
  25. data/docs/modules/runtime-metrics.md +22 -0
  26. data/docs/modules/sampling.md +22 -0
  27. data/docs/modules/sidekiq-legacy.md +1 -1
  28. data/docs/modules/sql-monitoring.md +76 -0
  29. data/docs/modules/telemetry-events.md +1 -1
  30. data/docs/performance.md +30 -3
  31. data/docs/privacy-lgpd.md +6 -3
  32. data/docs/protocol-v1.md +2 -2
  33. data/docs/release-1.0-readiness.md +17 -15
  34. data/docs/release-1.1-readiness.md +51 -0
  35. data/docs/security-review.md +7 -3
  36. data/docs/troubleshooting.md +6 -0
  37. data/lib/chronos/agent.rb +12 -2
  38. data/lib/chronos/application/apm_aggregator.rb +179 -29
  39. data/lib/chronos/configuration/apm_validation.rb +51 -1
  40. data/lib/chronos/configuration.rb +17 -1
  41. data/lib/chronos/core/metric_aggregate.rb +69 -7
  42. data/lib/chronos/core/sql_query_analyzer.rb +309 -0
  43. data/lib/chronos/ports/query_inspector.rb +23 -0
  44. data/lib/chronos/rails/active_record_query_inspector.rb +235 -0
  45. data/lib/chronos/rails/notifications_subscriber.rb +163 -2
  46. data/lib/chronos/rails.rb +1 -0
  47. data/lib/chronos/version.rb +1 -1
  48. data/lib/chronos.rb +2 -0
  49. metadata +21 -4
@@ -24,10 +24,60 @@
24
24
  "type": "object",
25
25
  "required": ["total", "min", "max", "average"]
26
26
  },
27
+ "percentiles_ms": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "required": ["p50", "p95", "p99"],
31
+ "properties": {
32
+ "p50": {"type": "number", "minimum": 0},
33
+ "p95": {"type": "number", "minimum": 0},
34
+ "p99": {"type": "number", "minimum": 0}
35
+ }
36
+ },
27
37
  "histogram": {"type": "array", "maxItems": 20},
28
38
  "breakdown_ms": {"type": "object"},
29
39
  "signals": {"type": "object"},
30
- "status_codes": {"type": "object"}
40
+ "status_codes": {"type": "object"},
41
+ "severity_counts": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "error": {"type": "integer", "minimum": 0},
46
+ "warning": {"type": "integer", "minimum": 0},
47
+ "info": {"type": "integer", "minimum": 0},
48
+ "suggestion": {"type": "integer", "minimum": 0}
49
+ }
50
+ },
51
+ "diagnostics": {
52
+ "type": "array",
53
+ "maxItems": 20,
54
+ "items": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["code", "severity", "category", "message", "evidence", "count"],
58
+ "properties": {
59
+ "code": {"type": "string", "maxLength": 128},
60
+ "severity": {"enum": ["error", "warning", "info", "suggestion"]},
61
+ "category": {"type": "string", "maxLength": 64},
62
+ "message": {"type": "string", "maxLength": 512},
63
+ "recommendation": {"type": "string", "maxLength": 512},
64
+ "evidence": {"type": "object"},
65
+ "count": {"type": "integer", "minimum": 1}
66
+ }
67
+ }
68
+ },
69
+ "query_analysis": {"type": "object"},
70
+ "tracking": {
71
+ "type": "object",
72
+ "additionalProperties": false,
73
+ "required": ["active_traces", "dropped_trace_trackers", "expired_trace_trackers", "dropped_query_fingerprints"],
74
+ "properties": {
75
+ "active_traces": {"type": "integer", "minimum": 0},
76
+ "dropped_trace_trackers": {"type": "integer", "minimum": 0},
77
+ "expired_trace_trackers": {"type": "integer", "minimum": 0},
78
+ "dropped_query_fingerprints": {"type": "integer", "minimum": 0}
79
+ }
80
+ }
31
81
  }
32
82
  }
33
83
  }
@@ -0,0 +1,25 @@
1
+ # ADR-007 — Feature detection nas integrações
2
+
3
+ ## Status
4
+
5
+ Aceito para 1.0.
6
+
7
+ ## Contexto
8
+
9
+ Rails 4.2/5.2, Active Job e Sidekiq 4/5 expõem conjuntos diferentes de constantes e hooks. Comparar apenas números de versão cria falsos positivos em instalações parciais.
10
+
11
+ ## Decisão
12
+
13
+ Integrações opcionais verificam a presença da biblioteca e das APIs públicas necessárias antes de instalar middleware, subscribers ou extensões. A instalação é idempotente e não exige Zeitwerk.
14
+
15
+ ## Alternativas
16
+
17
+ Branches por versão e uso de APIs privadas foram rejeitados por fragilidade e custo de manutenção.
18
+
19
+ ## Consequências positivas
20
+
21
+ O núcleo permanece carregável em Ruby puro e combinações validadas compartilham adapters menores.
22
+
23
+ ## Consequências negativas
24
+
25
+ Cada caminho detectado exige contrato e aplicação de exemplo; uma biblioteca que imite parcialmente a API pode precisar de tratamento dedicado.
@@ -0,0 +1,25 @@
1
+ # ADR-008 — Context store por runtime
2
+
3
+ ## Status
4
+
5
+ Aceito para 1.0 legado.
6
+
7
+ ## Contexto
8
+
9
+ Requests e jobs concorrentes não podem compartilhar usuário, parâmetros, breadcrumbs ou trace ID, mas Ruby 2.2 não oferece uma estratégia fiber-local moderna uniforme.
10
+
11
+ ## Decisão
12
+
13
+ Definir uma porta de context store e usar armazenamento thread-local na linha 0.x/1.0 legado. Todo escopo restaura o valor anterior e limpa em `ensure`; somente IDs permitidos atravessam processos.
14
+
15
+ ## Alternativas
16
+
17
+ Estado global foi rejeitado por vazamento entre execuções. Fiber local foi adiado para a linha moderna.
18
+
19
+ ## Consequências positivas
20
+
21
+ O isolamento é testável e o núcleo não depende da primitiva concreta.
22
+
23
+ ## Consequências negativas
24
+
25
+ Threads e fibers criadas pela aplicação exigem propagação explícita; adapters futuros precisam preservar a mesma porta.
@@ -0,0 +1,25 @@
1
+ # ADR-009 — Sampling limitado localmente
2
+
3
+ ## Status
4
+
5
+ Aceito para 1.0.
6
+
7
+ ## Contexto
8
+
9
+ O agente precisa controlar volume durante carga alta e aceitar redução remota sem permitir expansão inesperada da coleta.
10
+
11
+ ## Decisão
12
+
13
+ Usar `sampling_rate` local como teto. Configuração remota pode somente reduzir a taxa, desabilitar tipos ou ativar kill switch. As opções são allowlisted, limitadas e nunca incluem código/regex.
14
+
15
+ ## Alternativas
16
+
17
+ Sampling exclusivamente no servidor desperdiça transporte. Sampling remoto sem teto local foi rejeitado por privacidade e previsibilidade.
18
+
19
+ ## Consequências positivas
20
+
21
+ O operador conserva controle e a decisão ocorre antes de HTTP/retry.
22
+
23
+ ## Consequências negativas
24
+
25
+ Sampling uniforme pode perder eventos raros; análises devem considerar a taxa efetiva.
@@ -0,0 +1,25 @@
1
+ # ADR-010 — Interoperabilidade futura com OpenTelemetry
2
+
3
+ ## Status
4
+
5
+ Adiado; limite aceito para 1.0 legado.
6
+
7
+ ## Contexto
8
+
9
+ O Chronos precisa correlacionar trace/request sem duplicar SDKs ou impor dependências modernas a Ruby 2.2–2.6.
10
+
11
+ ## Decisão
12
+
13
+ Manter IDs de correlação e portas independentes do fornecedor na 1.0. Não depender de OpenTelemetry nem instalar instrumentação global na linha legado. Uma ponte opcional será projetada na linha transitional/modern e deverá traduzir somente campos permitidos.
14
+
15
+ ## Alternativas
16
+
17
+ Adicionar o SDK como dependência obrigatória foi rejeitado por incompatibilidade e overhead. Copiar spans completos foi rejeitado por cardinalidade e privacidade.
18
+
19
+ ## Consequências positivas
20
+
21
+ O protocolo v1 permanece estável e aplicações legadas não recebem novas dependências.
22
+
23
+ ## Consequências negativas
24
+
25
+ Não há propagação automática com ecossistemas OpenTelemetry na 1.0; integrações futuras exigirão contrato e matriz próprios.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted for version 0.7.
5
+ Accepted for version 0.7; trace-drain behavior amended by ADR-019.
6
6
 
7
7
  ## Context
8
8
 
@@ -12,7 +12,7 @@ Sending every request, SQL query, and job as an independent event increases netw
12
12
 
13
13
  Aggregate request, query, and job observations by bounded low-cardinality dimensions. Store only counters, error counters, duration sum/min/max, fixed histogram buckets, component totals, status counters, and heuristic signal counters. Calculate averages locally and percentiles in the SaaS.
14
14
 
15
- Use a fixed group limit, fixed query-fingerprint limit per trace, fixed batch size, and no new timer thread. Drain on observation threshold, explicit flush, and close. Clear incomplete trace trackers during drain. Normalize SQL comments and literal values before fingerprinting and never read binds. Use a `metric_batch` event through the existing sanitization, queue, retry, circuit breaker, and backlog pipeline.
15
+ Use a fixed group limit, fixed query-fingerprint limit per trace, fixed batch size, and no new timer thread. Drain on observation threshold, explicit flush, and close. ADR-019 later preserves incomplete trace trackers across drains under capacity and idle-TTL limits. Normalize SQL comments and literal values before fingerprinting and never read binds. Use a `metric_batch` event through the existing sanitization, queue, retry, circuit breaker, and backlog pipeline.
16
16
 
17
17
  ## Alternatives
18
18
 
@@ -24,4 +24,4 @@ Delivery volume is reduced, memory remains bounded, request breakdown is availab
24
24
 
25
25
  ## Negative consequences
26
26
 
27
- Process crashes can lose undrained aggregates, incomplete trackers are discarded on drain, local signals are heuristic, and a defensive SQL normalizer cannot understand every database dialect.
27
+ Process crashes can lose undrained aggregates, idle/over-capacity trackers can be discarded and are counted, local signals are heuristic, and a defensive SQL normalizer cannot understand every database dialect.
@@ -1,9 +1,13 @@
1
1
  # ADR-018: Pre-1.0 hardening gates
2
2
 
3
+ ## Status
4
+
5
+ Concluído pela versão 1.0.0.
6
+
3
7
  ## Decision
4
8
 
5
- Keep the release at `0.9.0.pre.2` while any mandatory 1.0 evidence remains external or incomplete. Add bounded local ignore rules, Active Job envelope propagation, real Sidekiq compatibility jobs, repeatable comparative/load benchmarks, and explicit release/security policies.
9
+ Keep the release in prerelease while any mandatory 1.0 evidence remains external or incomplete. Promote only after bounded local ignore rules, Active Job envelope propagation, real Sidekiq compatibility jobs, repeatable comparative/load benchmarks, and explicit release/security policies are present and green.
6
10
 
7
11
  ## Consequences
8
12
 
9
- The stable API is not promised before the full legacy matrix passes. Active Job gains one namespaced serialized field without changing arguments. Application callbacks remain bounded in count but their execution cost belongs to the application. Package checksums are used until a trusted signing lifecycle is available.
13
+ The complete candidate matrix passed before the 1.0 promotion. Active Job has one namespaced serialized field without changing arguments. Application callbacks remain bounded in count but their execution cost belongs to the application. Trusted Publishing, package checksums and SBOMs are used until a trusted signing lifecycle is available.
@@ -0,0 +1,44 @@
1
+ # ADR-019 — Bounded query diagnostics and opt-in database inspection
2
+
3
+ ## Status
4
+
5
+ Accepted.
6
+
7
+ ## Context
8
+
9
+ Fingerprint repetition and duration identify useful SQL symptoms but do not tell the consumer which access columns were observed, whether an existing index covers them, or what the database planner estimated. Running deep analysis inside a legacy application can add latency, expose data, increase cardinality, and violate the fixed-memory requirement.
10
+
11
+ ## Decision
12
+
13
+ Analyze normalized SELECT text by default with a dependency-free bounded heuristic. Emit structured diagnostics with stable code, severity (`error`, `warning`, `info`, `suggestion`), category, bounded evidence, count, and optional recommendation.
14
+
15
+ Define `Ports::QueryInspector` and keep its ActiveRecord implementation in `Chronos::Rails`. Index, statistics, and plan inspection are independent explicit opt-ins. Inspect each eligible fingerprint at most once per subscriber, cap inspected fingerprints, and require a duration threshold. Plans use `EXPLAIN` only, never `EXPLAIN ANALYZE`; DDL and automatic index creation are prohibited. Retain only allowlisted schema/planner fields and exception class names. Never retain raw SQL, binds, plan predicates, arbitrary rows, or exception messages.
16
+
17
+ Preserve trace trackers across metric drains with capacity and idle TTL. Report loss counters. Measure outer transaction time with bounded per-connection notification state. Derive approximate p50/p95/p99 from fixed histograms without retaining duration samples.
18
+
19
+ Keep new metric fields optional in the v1 JSON Schema for backward compatibility, although the new agent emits them. Consumer models use diagnostic code/severity as stable fields and treat message text as a fallback presentation value.
20
+
21
+ ## Alternatives
22
+
23
+ - Server-only static analysis was rejected because existing-index comparison requires local schema evidence and immediate signals remain useful.
24
+ - Automatic `EXPLAIN ANALYZE` was rejected because it executes the query.
25
+ - Automatic `CREATE INDEX` was rejected because it can block writes, consume storage, and regress mutation workloads.
26
+ - Shipping raw SQL/plans was rejected for privacy and cardinality.
27
+ - A full SQL-parser dependency was rejected for legacy compatibility, installation cost, and dialect complexity.
28
+ - Unbounded trace/inspection caches were rejected for memory safety.
29
+
30
+ ## Positive consequences
31
+
32
+ - Consumers receive actionable, typed evidence with clear confidence limits.
33
+ - Default analysis adds no database round trip.
34
+ - Opt-in inspection is read-only, sampled by fingerprint/duration, and contains adapter failures.
35
+ - Errors, warnings, information, and suggestions can map directly to observer models.
36
+ - Trace correlation no longer disappears at every aggregate flush.
37
+
38
+ ## Negative consequences
39
+
40
+ - Static parsing can miss complex SQL, aliases, expressions, partial indexes, and dialect features.
41
+ - Even non-executing `EXPLAIN` and catalog reads consume a connection/planner budget and have no portable legacy timeout.
42
+ - Index recommendations do not know true selectivity, write cost, storage, or production distribution.
43
+ - Approximate percentiles use histogram upper bounds and may be coarse.
44
+ - Transaction elapsed time is measured between notification callbacks, not database wire boundaries.
data/docs/architecture.md CHANGED
@@ -20,7 +20,10 @@ flowchart TB
20
20
  Sidekiq --> Envelope[Versioned job-envelope context]
21
21
  Application --> Telemetry[Core / TelemetryEvent]
22
22
  Telemetry --> APM[Application / ApmAggregator]
23
- APM --> Metrics[Core / MetricAggregate and SqlNormalizer]
23
+ APM --> Metrics[Core / MetricAggregate, SqlNormalizer, and SqlQueryAnalyzer]
24
+ Rails --> Inspector[ActiveRecordQueryInspector opt-in]
25
+ Inspector --> QueryPort[Ports / QueryInspector]
26
+ Inspector --> Metrics
24
27
  NetHTTP[Explicit Net::HTTP instance wrapper] --> Facade
25
28
  Rails --> Cache[Core / CacheNormalizer]
26
29
  Application --> Dependencies[Application / DependencyReporter]
@@ -53,7 +56,7 @@ Rails timings become immutable `TelemetryEvent` values. `CaptureTelemetry` appli
53
56
 
54
57
  Sidekiq client middleware writes a versioned, allowlisted trace/request context beside the job's public arguments. Server middleware restores a job scope, limits arguments and tags, emits a job observation, and routes a failure through the existing notice pipeline before re-raising it. Sidekiq retains queue, retry, thread, and connection lifecycle ownership.
55
58
 
56
- Version 0.7 routes request, query, and job observations through `ApmAggregator`. `SqlNormalizer` removes values before grouping, while `MetricAggregate` owns fixed numerical statistics. Aggregates and per-trace query trackers are bounded and mutex-protected. Threshold or lifecycle drains create `metric_batch` telemetry that passes through the existing sanitizer and delivery pipeline. No APM-specific thread is created.
59
+ Version 0.7 routes request, query, and job observations through `ApmAggregator`. `SqlNormalizer` removes values before grouping, `SqlQueryAnalyzer` derives bounded access evidence, and `MetricAggregate` owns fixed numerical statistics, approximate percentiles, severities, diagnostics, and one representative analysis. Active trace trackers survive group drains, expire by TTL, and expose loss counters. `ActiveRecordQueryInspector` is an optional Rails adapter behind `Ports::QueryInspector`; it reads public index metadata and allowlisted PostgreSQL/MySQL statistics/plans without `ANALYZE` or DDL. Threshold or lifecycle drains create `metric_batch` telemetry that passes through the existing sanitizer and delivery pipeline. No APM-specific thread is created.
57
60
 
58
61
  Version 0.8 prepends a wrapper only to each explicitly selected `Net::HTTP` object. The wrapper records bounded outcome metadata and feeds the existing APM aggregator; it does not modify `Net::HTTP` globally. `CacheNormalizer` turns public Rails cache notifications into bounded metadata before delivery. `DependencyReporter` reads already loaded runtime metadata once per agent under a mutex and queues it as a separate event.
59
62
 
@@ -1,36 +1,43 @@
1
1
  # Compatibility
2
2
 
3
- Chronos Ruby 0.x is the legacy line. Technical compatibility does not make an end-of-life Ruby or Rails release secure.
3
+ Chronos Ruby 1.1 is the current stable legacy line. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure. The planned Ruby 2.7/Rails 6 transitional matrix remains deferred until it has dedicated CI and a real application gate.
4
4
 
5
- | Ruby | Rails integration | Status | Evidence |
6
- |---|---|---|---|
7
- | 2.2.10 | Rails 4.2 | Best effort | Core CI, Rails 4.2 example, dedicated smoke gate |
8
- | 2.3.8 | Rails 4.2 / 5.0 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
9
- | 2.4.10 | Rails 4.2 / 5.0 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
10
- | 2.5.9 | Rails 5.2 | Best effort | Core CI, Rails 5.2 example, dedicated smoke gate |
11
- | 2.6.10 | Rails 5.2 | Best effort | Core CI and feature-detection contract; dedicated app gate incomplete |
12
- | 2.7 and newer | None in 0.x | Unsupported | Belongs to transitional or modern lines |
13
-
14
- | Ruby | Sidekiq integration | Status | Evidence |
15
- |---|---|---|---|
16
- | 2.2.10 | Sidekiq 4.2.10 | Best effort | Unit/integration contracts and dedicated real-gem Docker job |
17
- | 2.5.9 | Sidekiq 5.2.10 | Best effort | Unit/integration contracts and dedicated real-gem Docker job |
5
+ ## Core and Rack
18
6
 
19
- Version 0.5 includes Rails 4.2 and 5.2 applications plus a dedicated matrix, but this document conservatively keeps the combinations at `Best effort` until all release-gate evidence, including fake-server payload validation, is green. Rails 5.0 uses the same feature-detected public APIs but does not yet have its own example application.
7
+ | Ruby | Status | Evidence |
8
+ |---|---|---|
9
+ | 2.2.10 | Supported | Full unit, integration, contract, Rack, concurrency, fork, transport, privacy, and lint gate |
10
+ | 2.3.8 | Supported | Same dedicated Docker gate |
11
+ | 2.4.10 | Supported | Same dedicated Docker gate |
12
+ | 2.5.9 | Supported | Same dedicated Docker gate |
13
+ | 2.6.10 | Supported | Same dedicated Docker gate |
14
+ | 2.7 and newer | Unsupported in 1.x legacy | Belongs to the transitional or modern lines |
20
15
 
21
- Version `0.9.0.pre.2` adds dedicated Sidekiq 4.2.10 and 5.2.10 real-gem jobs. Status remains `Best effort` until both external jobs pass on the release candidate.
16
+ ## Rails
22
17
 
23
- Active Job propagation uses the standard `serialize`, `deserialize`, and `perform_now` extension points with a namespaced bounded field. Rails 4.2/5.2 example jobs and unit contracts provide evidence; the complete external matrix must still pass before stable support is declared.
18
+ | Ruby | Rails | Status | Evidence |
19
+ |---|---|---|---|
20
+ | 2.2.10 | 4.2 | Supported | Real application boot, successful/error request, SQL, view, cache, Active Job, mailer, fake endpoint, flush, and shutdown |
21
+ | 2.3.8 | 4.2 | Supported | Same dedicated application gate |
22
+ | 2.5.9 | 5.2 | Supported | Same dedicated application gate |
23
+ | 2.6.10 | 5.2 | Supported | Same dedicated application gate |
24
+ | Other Ruby/Rails pairs | — | Unsupported | No complete release gate; feature detection alone is not a support claim |
24
25
 
25
- Version `0.7.0.pre.1` keeps the same Ruby/Rails matrix and implements APM aggregation without modern concurrency or SQL-parser dependencies. Its compatibility remains `Best effort` until request/SQL/job aggregate payloads pass the dedicated fake-server gates for every listed runtime.
26
+ ## Sidekiq
27
+
28
+ | Ruby | Sidekiq | Status | Evidence |
29
+ |---|---|---|---|
30
+ | 2.2.10 | 4.2.10 | Supported | Real-gem client/server middleware smoke with context, success/failure, and deduplication |
31
+ | 2.5.9 | 5.2.10 | Supported | Same dedicated application gate |
32
+ | Other Ruby/Sidekiq pairs | — | Unsupported | No complete release gate |
26
33
 
27
- Version `0.8.0.pre.1` uses per-object `Module#prepend`, legacy `Net::HTTP`, standard-library SHA-256, and loaded-spec feature detection. It adds no runtime dependency and keeps the same matrix. Outbound HTTP, cache, and dependency gates must pass every listed runtime before support is promoted.
34
+ Active Job uses the public `serialize`, `deserialize`, and `perform_now` extension points with a bounded namespaced field. Support follows the validated Rails pairs above. Adapters that bypass these hooks require their own evidence.
28
35
 
29
- Version `0.9.0.pre.1` adds only standard-library URI/SecureRandom processing, bounded hashes, and the existing synchronous delivery path. Capistrano is optional and feature-detected; Kamal and GitHub Actions integrations are commands/examples. The Ruby/Rails matrix remains unchanged and `Best effort` until deploy/correlation payload gates pass every listed runtime.
36
+ The release workflow repeats every supported pair before publishing. Historical 1.0 evidence remains in [Version 1.0 readiness](release-1.0-readiness.md); the 1.1 candidate is recorded in [Version 1.1 readiness](release-1.1-readiness.md).
30
37
 
31
38
  Status meanings:
32
39
 
33
- - Supported: the complete required compatibility gate passes.
34
- - Best effort: intended to work, but the complete gate has not passed yet.
40
+ - Supported: every mandatory compatibility gate for the exact pair passes.
41
+ - Best effort: intended to work but missing a complete gate; no 1.1 pair is advertised this way.
35
42
  - Deprecated: still tested while removal is planned.
36
- - Unsupported: outside this release line.
43
+ - Unsupported: outside the tested 1.1 contract.
@@ -63,6 +63,16 @@
63
63
  | `apm_long_transaction_threshold_ms` | Optional | `1000.0` | Transaction-labelled SQL duration that produces a signal |
64
64
  | `apm_n_plus_one_threshold` | Optional | `5` | Repeated fingerprint count producing one possible-N+1 signal; minimum 2 |
65
65
  | `apm_histogram_buckets` | Optional | Fixed millisecond boundaries | Increasing positive duration boundaries; at most 19 plus `+Inf` |
66
+ | `apm_trace_ttl_seconds` | Optional | `60.0` | Maximum idle time retained for an incomplete trace or transaction tracker |
67
+ | `apm_query_analysis_enabled` | Optional | `true` | Analyzes bounded normalized SELECT shapes and emits informational/index-candidate diagnostics |
68
+ | `apm_query_analysis_max_queries` | Optional | `100` | Maximum normalized fingerprints analyzed and cached per subscriber; range 1–500 |
69
+ | `apm_query_inspection_enabled` | Optional | `false` | Opts in to read-only comparison with ActiveRecord index metadata |
70
+ | `apm_query_statistics_enabled` | Optional | `false` | Adds adapter-specific estimated table-row statistics; requires query inspection |
71
+ | `apm_query_plan_enabled` | Optional | `false` | Adds allowlisted `EXPLAIN` evidence for eligible SELECTs; never uses `ANALYZE` |
72
+ | `apm_query_inspection_min_duration_ms` | Optional | `500.0` | Minimum observed query duration before an opt-in database inspection |
73
+ | `apm_query_inspection_max_queries` | Optional | `20` | Maximum unique fingerprints inspected per subscriber; range 1–100 |
74
+ | `apm_transaction_tracking_enabled` | Optional | `true` | Measures bounded outer transaction elapsed time by notification connection |
75
+ | `apm_transaction_max_connections` | Optional | `100` | Maximum concurrently tracked transaction connections; range 1–500 |
66
76
  | `external_http_enabled` | Optional | `false` | Allows explicit per-instance outbound `Net::HTTP` instrumentation |
67
77
  | `external_http_trace_headers` | Optional | `true` | Propagates Chronos trace/request headers on instrumented requests |
68
78
  | `cache_key_mode` | Optional | `:none` | `:none` omits keys; `:sha256` emits a project-scoped key hash |
@@ -102,6 +112,17 @@ Chronos.configure do |config|
102
112
  config.apm_slow_query_threshold_ms = 500.0
103
113
  config.apm_long_transaction_threshold_ms = 1000.0
104
114
  config.apm_n_plus_one_threshold = 5
115
+ config.apm_trace_ttl_seconds = 60.0
116
+ config.apm_query_analysis_enabled = true
117
+ config.apm_query_analysis_max_queries = 100
118
+ # Database access is explicit because each inspected fingerprint adds read-only work.
119
+ config.apm_query_inspection_enabled = false
120
+ config.apm_query_statistics_enabled = false
121
+ config.apm_query_plan_enabled = false
122
+ config.apm_query_inspection_min_duration_ms = 500.0
123
+ config.apm_query_inspection_max_queries = 20
124
+ config.apm_transaction_tracking_enabled = true
125
+ config.apm_transaction_max_connections = 100
105
126
  config.external_http_enabled = false
106
127
  config.external_http_trace_headers = true
107
128
  config.cache_key_mode = :none
@@ -33,13 +33,19 @@ Version 0.9 emits exceptions, cache telemetry, dependency/deploy events, and agg
33
33
  | Sidekiq arguments | Collected, sanitized, and bounded | First 20 job arguments; collections/depth/strings limited |
34
34
  | Sidekiq tags | Collected and bounded | Job payload or public worker options |
35
35
  | Sidekiq trace/request IDs | Propagated when present; trace generated otherwise | Chronos job-envelope metadata |
36
- | APM counts, error counts/rates, duration total/min/max/average | Aggregated by default | Request, query, and job observations |
37
- | Fixed duration histogram and status counts | Aggregated by default | Local bounded counters |
36
+ | APM counts, error counts/rates, duration total/min/max/average and approximate p50/p95/p99 | Aggregated by default | Request, query, and job observations plus fixed histogram |
37
+ | Fixed duration histogram, status counts, severity counts and bounded diagnostics | Aggregated by default | Local bounded counters and classifiers |
38
38
  | Component breakdown | database/view/external_http/cache/queue/application when observable | Trace-local bounded totals |
39
39
  | Normalized SQL and SHA-256 fingerprint | Collected without comments, literals, or binds | `sql.active_record` payload |
40
40
  | SQL adapter, operation, inferred table, AR name, cache flag, role/shard | Collected when exposed | Public notification payload and connection feature detection |
41
41
  | Slow SQL source frame | Collected only for threshold-selected slow queries | Bounded application call frame |
42
42
  | APM diagnostic signals | Heuristic counters | Local threshold and repetition detection |
43
+ | SELECT tables and equality/range/join/order columns; index candidates | Collected by default from normalized SQL; bounded | Local static query analyzer |
44
+ | Existing index names/columns/uniqueness | Disabled by default; read-only opt-in | Public ActiveRecord schema metadata |
45
+ | Estimated table rows | Disabled by default; read-only opt-in | PostgreSQL `pg_class` or MySQL `information_schema` |
46
+ | Allowlisted plan node type/table/index/estimated rows/cost | Disabled by default; `EXPLAIN` without `ANALYZE` | Local database planner |
47
+ | Complete outer transaction approximate duration | Collected by default with bounded state | BEGIN/COMMIT/ROLLBACK notifications keyed by local connection identity |
48
+ | Trace tracking loss/expiration counters | Aggregated by default | Local bounded tracker lifecycle |
43
49
  | External HTTP host, method, status, duration, timeout, connection-error flag, error class | Disabled by default; per-instance opt-in | Instrumented `Net::HTTP` object |
44
50
  | Chronos trace/request headers | Propagated when available | Current execution context |
45
51
  | Cache operation, backend, namespace, hit/miss, duration | Collected; key/value omitted | ActiveSupport cache notifications |
@@ -50,7 +56,7 @@ Version 0.9 emits exceptions, cache telemetry, dependency/deploy events, and agg
50
56
  | Deploy environment, revision, version, repository, actor, deploy ID, service, region, instance | Explicit synchronous deploy API | Application/deployment integration arguments |
51
57
  | Integration verification ID, fixed test marker, synthetic exception class/message, and receipt correlation | Only when `Chronos.verify_integration` or its Rake task is invoked | Locally generated bounded values |
52
58
 
53
- The gem never collects request bodies, response bodies, raw query strings, cookies, HTTP authorization headers, environment variables in bulk, Git state, source code, raw SQL, SQL bind values, database rows, raw cache keys/values, mail bodies/recipients, Active Job arguments, gem paths, or lockfile contents. Active Job IDs, Sidekiq JIDs/arguments, loaded gem names/versions, and deploy fields are documented integration data.
59
+ The gem never transmits request bodies, response bodies, raw query strings, cookies, HTTP authorization headers, environment variables in bulk, Git state, source code, raw SQL, SQL bind values, database rows, plan predicates, raw cache keys/values, mail bodies/recipients, Active Job arguments, gem paths, or lockfile contents. Opt-in plan inspection temporarily supplies the already observed local SELECT to `EXPLAIN` without `ANALYZE` and discards it. Active Job IDs, Sidekiq JIDs/arguments, loaded gem names/versions, and deploy fields are documented integration data.
54
60
 
55
61
  APM dimensions never include user ID, job ID, raw URL, exception message, bind value, or cache key. Normalized routes replace common numeric/UUID segments. Normalized SQL can retain schema, table, and column identifiers; review those identifiers as part of the privacy audit.
56
62
 
@@ -4,4 +4,4 @@ After `1.0.0`, a public API scheduled for removal remains available for at least
4
4
 
5
5
  Warnings are emitted at most once per process through the configured safe logger and must not include application payloads. Security fixes, behavior that can expose secrets, and upstream runtime incompatibilities may require faster action; the security advisory and changelog must explain the exception. Ruby/Rails support changes are recorded in `docs/compatibility.md` before removal.
6
6
 
7
- Prereleases may revise APIs without the full stable window, but every incompatible revision must remain explicit. Version `0.9.0.pre.2` introduces no removal.
7
+ Prereleases may revise APIs without the full stable window, but every incompatible revision must remain explicit. Version `1.0.0` starts the stable deprecation window and introduces no removal.
@@ -29,6 +29,12 @@ For network-free per-instance outbound HTTP instrumentation, run:
29
29
  bundle _1.17.3_ exec ruby examples/plain-ruby/external_http.rb
30
30
  ```
31
31
 
32
+ For a network-free normalized-query/index diagnostic example, run:
33
+
34
+ ```bash
35
+ bundle _1.17.3_ exec ruby examples/plain-ruby/query_analysis.rb
36
+ ```
37
+
32
38
  For a network-free deploy event with complete release correlation, run:
33
39
 
34
40
  ```bash
@@ -1,6 +1,6 @@
1
1
  # Migration from Airbrake
2
2
 
3
- Version `0.9.0.pre.2` provides a staged migration path. Run both agents only long enough to compare delivery, then remove Airbrake to avoid duplicate reports and overhead.
3
+ Version `1.0.0` provides a staged migration path. Run both agents only long enough to compare delivery, then remove Airbrake to avoid duplicate reports and overhead.
4
4
 
5
5
  | Airbrake concept | Chronos equivalent |
6
6
  |---|---|
@@ -4,13 +4,13 @@ Version `0.8.0.pre.1` aggregates request, SQL, job, and explicitly instrumented
4
4
 
5
5
  ## Request metrics
6
6
 
7
- Request groups use normalized route and HTTP method. Status codes are counted inside the group so error rate can be calculated without making status part of an unbounded key. Each metric contains count, error count/rate, total, minimum, maximum, average, fixed histogram buckets, and component breakdown. Percentiles remain server-side because retaining every duration locally would violate the memory boundary.
7
+ Request groups use normalized route and HTTP method. Status codes are counted inside the group so error rate can be calculated without making status part of an unbounded key. Each metric contains count, error count/rate, total, minimum, maximum, average, fixed histogram buckets, approximate p50/p95/p99 upper bounds, severity counts, bounded diagnostics, and component breakdown. The SaaS may calculate more precise percentiles from merged histograms; the agent never retains raw duration arrays.
8
8
 
9
9
  Rack records request metrics for non-Rails applications. Rails controller notifications and Rack share `record_event_once`, so the same request is counted only once. Controller notification data wins when available because its route is more precise.
10
10
 
11
11
  ## SQL metrics and signals
12
12
 
13
- `Chronos::Core::SqlNormalizer` removes block/line comments, quoted literal values, numeric values, booleans, nulls, and repeated `IN` values before producing a SHA-256 fingerprint. Binds are never read. Query dimensions can contain adapter, operation, inferred table, bounded normalized query, fingerprint, Active Record operation name, cache flag, connection role/shard, and a bounded source frame for slow sampled queries.
13
+ `Chronos::Core::SqlNormalizer` removes block/line comments, quoted literal values, numeric values, booleans, nulls, and repeated `IN` values before producing a SHA-256 fingerprint. Binds are never read. `SqlQueryAnalyzer` extracts bounded SELECT access columns. The optional `ActiveRecordQueryInspector` compares existing indexes and adds allowlisted statistics/plans without retaining raw SQL. Query dimensions can contain adapter, operation, inferred table, bounded normalized query, fingerprint, Active Record operation name, cache flag, connection role/shard, and a bounded source frame for slow sampled queries.
14
14
 
15
15
  Local signals are intentionally heuristic:
16
16
 
@@ -18,7 +18,8 @@ Local signals are intentionally heuristic:
18
18
  - `repeated_query` after the same fingerprint appears again in one trace;
19
19
  - `possible_n_plus_one` once when the configured repetition threshold is reached;
20
20
  - `long_transaction` for transaction-labelled SQL over its threshold;
21
- - `connection_error` and `deadlock` from bounded exception class names.
21
+ - `connection_error`, query/pool/lock timeout, constraint violation, and `deadlock` from bounded exception class names;
22
+ - index candidate/coverage and sequential-scan evidence from bounded query analysis.
22
23
 
23
24
  The SaaS must confirm and correlate these signals. They are not proof of an N+1, deadlock, or application defect.
24
25
 
@@ -29,9 +30,11 @@ The SaaS must confirm and correlate these signals. They are not proof of an N+1,
29
30
  - at most `apm_max_queries_per_request` fingerprints per trace;
30
31
  - at most 19 configured histogram boundaries plus `+Inf`;
31
32
  - at most `apm_batch_size` groups per payload, with a hard maximum of 50;
32
- - trackers are removed when their request completes and all remaining trackers are cleared on aggregate drain.
33
+ - trackers are removed when their request completes or their idle TTL expires; aggregate drain preserves active trackers;
34
+ - at most 20 diagnostics and one representative bounded query analysis per metric group;
35
+ - at most `apm_query_inspection_max_queries` database inspections per Rails subscriber.
33
36
 
34
- New groups beyond capacity are dropped and counted in `dropped_groups`. Existing groups continue accumulating. The state is process-local and is lost on restart.
37
+ New groups beyond capacity are dropped and counted in `dropped_groups`. Tracking loss is reported through `dropped_trace_trackers`, `expired_trace_trackers`, and `dropped_query_fingerprints`. Existing groups continue accumulating. The state is process-local and is lost on restart.
35
38
 
36
39
  ## Breakdown
37
40
 
@@ -51,6 +54,16 @@ Chronos.configure do |config|
51
54
  config.apm_long_transaction_threshold_ms = 1000.0
52
55
  config.apm_n_plus_one_threshold = 5
53
56
  config.apm_histogram_buckets = [5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0]
57
+ config.apm_trace_ttl_seconds = 60.0
58
+ config.apm_query_analysis_enabled = true
59
+ config.apm_query_analysis_max_queries = 100
60
+ config.apm_query_inspection_enabled = false
61
+ config.apm_query_statistics_enabled = false
62
+ config.apm_query_plan_enabled = false
63
+ config.apm_query_inspection_min_duration_ms = 500.0
64
+ config.apm_query_inspection_max_queries = 20
65
+ config.apm_transaction_tracking_enabled = true
66
+ config.apm_transaction_max_connections = 100
54
67
  end
55
68
  ```
56
69
 
@@ -0,0 +1,23 @@
1
+ # Breadcrumbs
2
+
3
+ ## Problema e limite
4
+
5
+ Breadcrumbs preservam uma trilha curta do fluxo que antecedeu uma falha. Não são logs, tracing completo ou armazenamento de payloads brutos.
6
+
7
+ ## Fluxo e classes
8
+
9
+ `Chronos.add_breadcrumb` normaliza a categoria e os metadados em `Chronos::Core::Breadcrumb`. `BreadcrumbBuffer` mantém um anel de capacidade fixa no context store. O `NoticeBuilder` copia a fotografia atual; sanitizer e serializer aplicam novamente limites antes da entrega.
10
+
11
+ ## Extensão, riscos e exemplo
12
+
13
+ Integrações podem registrar apenas categorias conhecidas e metadados de baixa cardinalidade. Mensagens e metadados continuam sendo dados da aplicação e podem conter informação pessoal se o chamador ignorar minimização.
14
+
15
+ ```ruby
16
+ Chronos.add_breadcrumb(
17
+ :category => "custom",
18
+ :message => "invoice queued",
19
+ :metadata => {"provider" => "example"}
20
+ )
21
+ ```
22
+
23
+ Os limites e a herança por captura são testados em `spec/unit/core/breadcrumb_spec.rb`, `spec/unit/agent_spec.rb` e nos specs de Rack.
@@ -0,0 +1,21 @@
1
+ # Contexto de execução
2
+
3
+ ## Problema e limite
4
+
5
+ O contexto relaciona uma exceção ou métrica ao request/job atual sem criar dependência do núcleo com Rack, Rails ou Sidekiq. Ele guarda somente valores delimitados e não é um repositório de estado da aplicação.
6
+
7
+ ## Fluxo e classes
8
+
9
+ `Chronos::Ports::ContextStore` define `get`, `set`, `clear` e `with_context`. Na linha legado, `Chronos::Adapters::ThreadLocalContextStore` isola o valor por thread. A fachada combina o contexto explícito com o escopo atual; o serializer sanitiza e limita tudo antes da fila. Rack limpa o escopo em `ensure`, enquanto Sidekiq e Active Job propagam apenas `trace_id` e `request_id`.
10
+
11
+ ## Extensão, riscos e exemplo
12
+
13
+ Um adaptador alternativo pode ser configurado quando implementar a porta completa. Ele precisa restaurar escopos aninhados e garantir limpeza após exceções. Thread local não acompanha automaticamente fibers ou threads criadas pela aplicação; nesses casos, propague somente os identificadores permitidos.
14
+
15
+ ```ruby
16
+ Chronos.with_context("trace_id" => "trace-42", "request_id" => "request-7") do
17
+ Chronos.notify(RuntimeError.new("failure"))
18
+ end
19
+ ```
20
+
21
+ Os contratos estão em `spec/unit/ports/context_store_spec.rb`, `spec/unit/adapters/thread_local_context_store_spec.rb` e `spec/integration/rack_middleware_concurrency_spec.rb`.
@@ -0,0 +1,23 @@
1
+ # Deploys
2
+
3
+ ## Problema e limite
4
+
5
+ Deploys criam o marco temporal usado para comparar erros e desempenho antes/depois de uma release. A gem informa o evento; reconciliação e análise pertencem ao SaaS.
6
+
7
+ ## Fluxo e classes
8
+
9
+ `Chronos.notify_deploy` passa atributos explícitos a `Core::DeployNormalizer`, sanitiza o evento e usa entrega síncrona com idempotência. `CorrelationContext` copia release, revision, deploy ID, ambiente, serviço, região e instância para todos os envelopes. Uma entrega bem-sucedida libera um novo snapshot de dependências.
10
+
11
+ ## Extensão, riscos e exemplo
12
+
13
+ Capistrano possui hook opcional; Kamal e GitHub Actions usam comandos documentados. A gem não lê Git nem variáveis automaticamente. O retorno `false` deve ser tratado conforme a política de deploy da aplicação.
14
+
15
+ ```ruby
16
+ Chronos.notify_deploy(
17
+ :environment => "production",
18
+ :revision => ENV["GIT_SHA"],
19
+ :version => ENV["APP_VERSION"]
20
+ )
21
+ ```
22
+
23
+ Veja `spec/unit/core/deploy_normalizer_spec.rb`, `spec/unit/integrations/capistrano_spec.rb` e `spec/integration/deploy_delivery_spec.rb`.
@@ -0,0 +1,22 @@
1
+ # Monitoramento de jobs
2
+
3
+ ## Problema e limite
4
+
5
+ Jobs precisam manter correlação entre enqueue e execução e registrar duração/falha sem abrir threads ou conexões por job. A versão 1.0 cobre Sidekiq 4/5 e Active Job disponível em Rails 4.2/5.2; Resque e Delayed Job permanecem fora do escopo estável.
6
+
7
+ ## Fluxo e classes
8
+
9
+ O middleware Sidekiq injeta um envelope Chronos separado dos argumentos públicos. O servidor restaura contexto, mede fila/execução e deduplica exceções aninhadas. A integração Active Job usa os hooks públicos de serialização e `perform_now`. As observações seguem para `ApmAggregator` e as exceções para o notice pipeline.
10
+
11
+ ## Extensão, riscos e exemplo
12
+
13
+ Adapters que substituem hooks públicos exigem testes próprios. Argumentos Sidekiq são limitados e sanitizados, mas a aplicação deve evitar segredos e dados pessoais desnecessários.
14
+
15
+ ```ruby
16
+ require "chronos/sidekiq"
17
+ Sidekiq.configure_server do |config|
18
+ config.server_middleware { |chain| chain.add Chronos::Integrations::Sidekiq::ServerMiddleware }
19
+ end
20
+ ```
21
+
22
+ Veja `spec/unit/integrations/sidekiq_spec.rb`, `spec/unit/integrations/active_job_spec.rb` e `spec/integration/sidekiq_delivery_spec.rb`.
@@ -0,0 +1,20 @@
1
+ # Monitoramento de requests
2
+
3
+ ## Problema e limite
4
+
5
+ Requests Rack/Rails alimentam métricas de duração, status e breakdown com dimensões limitadas. O módulo não lê bodies, cookies, autorização ou query string bruta.
6
+
7
+ ## Fluxo e classes
8
+
9
+ O middleware Rack cria contexto isolado e mede o request. Subscribers Rails enriquecem controller/action e evitam duplicação. `CaptureTelemetry` envia a observação a `ApmAggregator`; por padrão ela integra um `metric_batch`, e com APM desativado vira evento individual sanitizado.
10
+
11
+ ## Extensão, riscos e exemplo
12
+
13
+ Rotas devem ser normalizadas para evitar cardinalidade por ID. Aplicações Rack podem fornecer um normalizador por meio dos campos já aceitos, sem incluir parâmetros sensíveis.
14
+
15
+ ```ruby
16
+ use Chronos::Integrations::Rack::Middleware,
17
+ :include_user_agent => false
18
+ ```
19
+
20
+ Veja `spec/integration/rack_middleware_spec.rb`, `spec/integration/rack_middleware_concurrency_spec.rb` e `spec/integration/apm_aggregation_delivery_spec.rb`.