e11y 0.2.0 → 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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +80 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +165 -701
  7. data/RELEASE.md +41 -12
  8. data/Rakefile +249 -57
  9. data/config/README.md +1 -1
  10. data/config/loki-local-config.yaml +12 -0
  11. data/config/otel-collector-config.yaml +44 -0
  12. data/cucumber.yml +1 -0
  13. data/docker-compose.yml +18 -2
  14. data/docs/ADAPTERS.md +76 -0
  15. data/docs/ADAPTIVE_SAMPLING.md +59 -0
  16. data/docs/COMPARISON.md +104 -0
  17. data/docs/CONFIGURATION.md +52 -0
  18. data/docs/DISTRIBUTED_TRACING.md +44 -0
  19. data/docs/LIMITATIONS.md +13 -0
  20. data/docs/METRICS_DSL.md +84 -0
  21. data/docs/PERFORMANCE.md +60 -0
  22. data/docs/PII_FILTERING.md +40 -0
  23. data/docs/PRESETS.md +65 -0
  24. data/docs/QUICK-START.md +546 -587
  25. data/docs/RAILS_INTEGRATION.md +79 -0
  26. data/docs/SCHEMA_VALIDATION.md +63 -0
  27. data/docs/SLO-PROMQL-ALERTS.md +161 -0
  28. data/docs/TESTING.md +69 -0
  29. data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +36 -65
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/architecture/ADR-003-slo-observability.md +1402 -0
  32. data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
  33. data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
  34. data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
  35. data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
  36. data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +182 -743
  37. data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
  38. data/docs/architecture/ADR-010-developer-experience.md +522 -0
  39. data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +44 -86
  40. data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
  41. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  42. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  43. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +43 -59
  44. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
  45. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  46. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  47. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  48. data/docs/plans/2026-03-20-browser-overlay-svelte.md +281 -0
  49. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  50. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  51. data/docs/prd/01-overview-vision.md +19 -14
  52. data/docs/use_cases/README.md +22 -23
  53. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  54. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  55. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  56. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +33 -684
  57. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  58. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  59. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  60. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  61. data/docs/use_cases/UC-009-multi-service-tracing.md +30 -178
  62. data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
  63. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  64. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  65. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  66. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  67. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  68. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  69. data/docs/use_cases/UC-017-local-development.md +203 -777
  70. data/docs/use_cases/UC-018-testing-events.md +3 -3
  71. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  72. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  73. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  74. data/docs/use_cases/UC-022-event-registry.md +15 -21
  75. data/docs/use_cases/backlog.md +119 -87
  76. data/e11y.gemspec +2 -2
  77. data/gems/e11y-devtools/README.md +158 -0
  78. data/gems/e11y-devtools/config/routes.rb +15 -0
  79. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  80. data/gems/e11y-devtools/exe/e11y +34 -0
  81. data/gems/e11y-devtools/frontend/.gitignore +24 -0
  82. data/gems/e11y-devtools/frontend/README.md +51 -0
  83. data/gems/e11y-devtools/frontend/index.html +14 -0
  84. data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
  85. data/gems/e11y-devtools/frontend/package.json +28 -0
  86. data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
  87. data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
  88. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
  89. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
  90. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
  91. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
  92. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
  93. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
  94. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
  95. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
  96. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
  97. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
  98. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
  99. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
  100. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
  101. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
  102. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
  103. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
  104. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
  105. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
  106. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
  107. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
  108. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
  109. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
  110. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
  111. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
  112. data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
  113. data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
  114. data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
  115. data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
  116. data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
  117. data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
  118. data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
  119. data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
  120. data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
  121. data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
  122. data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
  123. data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
  124. data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
  125. data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
  126. data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
  127. data/gems/e11y-devtools/frontend/src/main.ts +8 -0
  128. data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
  129. data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
  130. data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
  131. data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
  132. data/gems/e11y-devtools/frontend/test_scale.js +3 -0
  133. data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
  134. data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
  135. data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
  136. data/gems/e11y-devtools/frontend/vite.config.ts +36 -0
  137. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  138. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  139. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  140. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  141. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  142. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  143. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  144. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  145. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  146. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  147. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +20 -0
  148. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -0
  149. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  150. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  151. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +67 -0
  152. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  153. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  154. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  155. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  156. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  157. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  158. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  159. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  160. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +91 -0
  161. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  162. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  163. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  164. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  165. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  166. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  167. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  168. data/lib/e11y/adapters/base.rb +33 -34
  169. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  170. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  171. data/lib/e11y/adapters/dev_log.rb +118 -0
  172. data/lib/e11y/adapters/file.rb +3 -6
  173. data/lib/e11y/adapters/in_memory.rb +52 -5
  174. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  175. data/lib/e11y/adapters/loki.rb +58 -23
  176. data/lib/e11y/adapters/null.rb +82 -0
  177. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  178. data/lib/e11y/adapters/otel_logs.rb +136 -23
  179. data/lib/e11y/adapters/sentry.rb +4 -7
  180. data/lib/e11y/adapters/stdout.rb +73 -7
  181. data/lib/e11y/adapters/yabeda.rb +153 -29
  182. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  183. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  184. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  185. data/lib/e11y/configuration.rb +272 -0
  186. data/lib/e11y/console.rb +10 -17
  187. data/lib/e11y/current.rb +53 -1
  188. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  189. data/lib/e11y/documentation/generator.rb +48 -0
  190. data/lib/e11y/event/base.rb +176 -82
  191. data/lib/e11y/event/value_sampling_config.rb +1 -5
  192. data/lib/e11y/events/rails/database/query.rb +1 -4
  193. data/lib/e11y/events/rails/job/failed.rb +2 -0
  194. data/lib/e11y/instruments/active_job.rb +44 -12
  195. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  196. data/lib/e11y/instruments/sidekiq.rb +135 -31
  197. data/lib/e11y/linters/base.rb +11 -0
  198. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  199. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  200. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  201. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  202. data/lib/e11y/logger/bridge.rb +26 -7
  203. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  204. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  205. data/lib/e11y/metrics/registry.rb +3 -5
  206. data/lib/e11y/metrics/test_backend.rb +62 -0
  207. data/lib/e11y/metrics.rb +56 -10
  208. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  209. data/lib/e11y/middleware/audit_signing.rb +43 -6
  210. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  211. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  212. data/lib/e11y/middleware/event_slo.rb +23 -9
  213. data/lib/e11y/middleware/otel_span.rb +23 -0
  214. data/lib/e11y/middleware/pii_filter.rb +104 -75
  215. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  216. data/lib/e11y/middleware/request.rb +70 -23
  217. data/lib/e11y/middleware/routing.rb +78 -21
  218. data/lib/e11y/middleware/sampling.rb +66 -17
  219. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  220. data/lib/e11y/middleware/trace_context.rb +45 -10
  221. data/lib/e11y/middleware/track_latency.rb +34 -0
  222. data/lib/e11y/middleware/validation.rb +7 -16
  223. data/lib/e11y/middleware/versioning.rb +26 -22
  224. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  225. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  226. data/lib/e11y/pii/patterns.rb +12 -1
  227. data/lib/e11y/pipeline/builder.rb +4 -4
  228. data/lib/e11y/presets/audit_event.rb +13 -2
  229. data/lib/e11y/railtie.rb +52 -14
  230. data/lib/e11y/registry.rb +306 -0
  231. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  232. data/lib/e11y/reliability/dlq/base.rb +71 -0
  233. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  234. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  235. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  236. data/lib/e11y/reliability/retry_handler.rb +26 -29
  237. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  238. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  239. data/lib/e11y/sampling/load_monitor.rb +5 -9
  240. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  241. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  242. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  243. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  244. data/lib/e11y/slo/config_loader.rb +40 -0
  245. data/lib/e11y/slo/config_validator.rb +58 -0
  246. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  247. data/lib/e11y/slo/event_driven.rb +8 -0
  248. data/lib/e11y/slo/tracker.rb +31 -4
  249. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  250. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  251. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  252. data/lib/e11y/trace_context/sampler.rb +35 -0
  253. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  254. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  255. data/lib/e11y/tracing/propagator.rb +144 -0
  256. data/lib/e11y/tracing.rb +47 -0
  257. data/lib/e11y/version.rb +1 -1
  258. data/lib/e11y/versioning/version_extractor.rb +32 -0
  259. data/lib/e11y.rb +123 -266
  260. data/lib/generators/e11y/event/event_generator.rb +22 -0
  261. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  262. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  263. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  264. data/lib/generators/e11y/install/install_generator.rb +34 -0
  265. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  266. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  267. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  268. data/lib/tasks/e11y_docs.rake +30 -0
  269. data/lib/tasks/e11y_events.rake +71 -0
  270. data/lib/tasks/e11y_lint.rake +91 -0
  271. data/lib/tasks/e11y_slo.rake +29 -0
  272. metadata +186 -39
  273. data/docs/ADR-003-slo-observability.md +0 -3337
  274. data/docs/ADR-010-developer-experience.md +0 -2166
  275. data/docs/API-REFERENCE-L28.md +0 -914
  276. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  277. data/docs/CONTRIBUTING.md +0 -312
  278. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  279. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  280. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  281. data/docs/PLAN.md +0 -148
  282. data/docs/README.md +0 -296
  283. data/docs/design/00-memory-optimization.md +0 -593
  284. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  285. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  286. data/docs/guides/README.md +0 -44
  287. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  288. data/lib/e11y/adapters/registry.rb +0 -141
@@ -0,0 +1,522 @@
1
+ # ADR-010: Developer Experience
2
+
3
+ **Status:** Accepted
4
+ **Date:** March 18, 2026
5
+ **Covers:** UC-017 (Local Development)
6
+ **Depends On:** ADR-001 (Core), ADR-004 (Adapter Architecture), ADR-008 (Rails Integration)
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ 1. [Context & Problem](#1-context--problem)
13
+ 2. [Architecture: Hub-and-Spoke](#2-architecture-hub-and-spoke)
14
+ 3. [DevLog Adapter](#3-devlog-adapter)
15
+ 4. [TUI — Interactive Log Viewer](#4-tui--interactive-log-viewer)
16
+ 5. [Browser Overlay](#5-browser-overlay)
17
+ 6. [MCP Server](#6-mcp-server)
18
+ 7. [CLI Entry Point](#7-cli-entry-point)
19
+ 8. [Monorepo Structure](#8-monorepo-structure)
20
+ 9. [Noise Reduction Philosophy](#9-noise-reduction-philosophy)
21
+ 10. [Technology Choices & Alternatives](#10-technology-choices--alternatives)
22
+ 11. [Trade-offs](#11-trade-offs)
23
+
24
+ ---
25
+
26
+ ## 1. Context & Problem
27
+
28
+ ### 1.1. Problem Statement
29
+
30
+ E11y routes events to production backends (Loki, Sentry, OpenTelemetry). During local development, those backends are unavailable or impractical to run. Developers need answers to:
31
+
32
+ - What events fired during this request?
33
+ - Which events contained errors?
34
+ - Did sampling or PII filtering suppress an event?
35
+ - How do parallel async traces relate to a single user interaction?
36
+
37
+ Before e11y-devtools, the only option was configuring a `StdoutAdapter` and scanning console output manually — a high-noise, low-signal workflow.
38
+
39
+ ### 1.2. Goals
40
+
41
+ 1. **Zero configuration** — works automatically in `development` and `test` environments.
42
+ 2. **Zero production overhead** — the write-path adapter is production-safe (tiny, no UI code).
43
+ 3. **Multiple access modes** — terminal (TUI), browser (overlay), AI assistant (MCP).
44
+ 4. **Noise reduction** — show developers what matters, hide what does not.
45
+
46
+ ### 1.3. Non-Goals
47
+
48
+ - Replace production observability backends (Loki, Sentry, OTel).
49
+ - Provide event schema registry or documentation generation (separate concern).
50
+ - Operate in production (all viewer code is in the opt-in `e11y-devtools` gem).
51
+
52
+ ---
53
+
54
+ ## 2. Architecture: Hub-and-Spoke
55
+
56
+ ```
57
+ ┌─────────────────────────┐
58
+ │ E11y Event Pipeline │
59
+ │ (production gem code) │
60
+ └────────────┬────────────┘
61
+ │ DevLog adapter
62
+
63
+ ┌─────────────────────────┐
64
+ │ log/e11y_dev.jsonl │ ← single source of truth
65
+ │ (JSONL, gzip rotation) │
66
+ └──────┬────────┬──────────┘
67
+ │ │
68
+ ┌────────────┘ └────────────────┐
69
+ ▼ ▼
70
+ ┌──────────────────┐ ┌───────────────────────────┐
71
+ │ TUI Viewer │ │ Browser Overlay │
72
+ │ bundle exec e11y │ │ Rails Engine /_e11y/ │
73
+ │ (ratatui_ruby) │ │ + injected JS <e11y- │
74
+ └──────────────────┘ │ overlay> custom element │
75
+ └───────────────────────────┘
76
+
77
+ ┌──────────────┘
78
+
79
+ ┌─────────────────────┐
80
+ │ MCP Server │
81
+ │ bundle exec e11y mcp│
82
+ │ (stdio / HTTP) │
83
+ └─────────────────────┘
84
+ ```
85
+
86
+ **Key design principle:** `log/e11y_dev.jsonl` is the single source of truth. The write path (DevLog adapter) lives in the production `e11y` gem — it is always available. All three viewers are independent and read the same file; they share no runtime state.
87
+
88
+ ---
89
+
90
+ ## 3. DevLog Adapter
91
+
92
+ The DevLog adapter is split into three components under `lib/e11y/adapters/dev_log/`.
93
+
94
+ ### 3.1. FileStore
95
+
96
+ `DevLog::FileStore` is the write path. It writes one JSON line per event and rotates the file when it exceeds configured limits.
97
+
98
+ Key implementation details:
99
+ - Thread-safety: `Mutex` + `File::LOCK_EX` around every write.
100
+ - Rotation: atomic rename to `.1`, `.2`, … up to `keep_rotated` numbered gzip files. Older files are deleted.
101
+ - Copy strategy: `IO.copy_stream` — no heap allocation for file content during rotation.
102
+
103
+ | Constant | Default | ENV override |
104
+ |---|---|---|
105
+ | `DEFAULT_MAX_SIZE` | 50 MB | `E11Y_MAX_SIZE` |
106
+ | `DEFAULT_MAX_LINES` | 10 000 | `E11Y_MAX_EVENTS` |
107
+ | `DEFAULT_KEEP_ROTATED` | 5 | `E11Y_KEEP_ROTATED` |
108
+
109
+ ### 3.2. Query
110
+
111
+ `DevLog::Query` is the read path, shared by all three viewers.
112
+
113
+ - **mtime-cached in-memory cache**: re-parses the JSONL file only when `File.mtime` changes.
114
+ - **Optional JSON accelerator**: uses `oj` when available, falls back to stdlib `JSON`.
115
+ - **Zero Rails dependency**: usable from the TUI process that has no Rails loaded.
116
+
117
+ Public API:
118
+
119
+ ```ruby
120
+ query = E11y::Adapters::DevLog::Query.new("log/e11y_dev.jsonl")
121
+
122
+ query.stored_events(limit: 1000, severity: nil, source: nil) # → Array of event Hashes
123
+ query.search("checkout") # → Array of matching events (full-text)
124
+ query.events_by_trace(id) # → Array of events for one trace_id
125
+ query.interactions # → Array of Interaction structs (grouped traces)
126
+ query.stats # → Hash with counts, error rate, top events
127
+ query.find_event(id) # → single event Hash or nil
128
+ query.updated_since?(time) # → Boolean (used by polling viewers)
129
+ query.clear! # → truncates the JSONL file
130
+ ```
131
+
132
+ `Interaction` is a plain Struct:
133
+ ```ruby
134
+ Interaction = Struct.new(:started_at, :trace_ids, :has_error?,
135
+ :source, keyword_init: true) do
136
+ def traces_count = trace_ids.size
137
+ end
138
+ ```
139
+
140
+ ### 3.3. DevLog Adapter Facade
141
+
142
+ `E11y::Adapters::DevLog` wraps FileStore for writing and delegates all read calls to Query:
143
+
144
+ ```ruby
145
+ adapter = E11y::Adapters::DevLog.new(
146
+ path: "log/e11y_dev.jsonl",
147
+ max_size: 50 * 1024 * 1024,
148
+ max_lines: 10_000,
149
+ keep_rotated: 5
150
+ )
151
+
152
+ adapter.write(event_data) # delegates to FileStore
153
+ adapter.recent_events(limit: 50) # delegates to Query
154
+ adapter.capabilities # → { dev_log: true, readable: true }
155
+ ```
156
+
157
+ ### 3.4. DevLogSource Middleware
158
+
159
+ `E11y::Middleware::DevLogSource` is a Rack middleware that stamps request metadata before events are tracked:
160
+
161
+ ```ruby
162
+ Thread.current[:e11y_source] = "web" # sets thread-local; downstream code (including DevLog#serialize) reads this
163
+ env["e11y.trace_id"] ||= Thread.current[:e11y_trace_id] # exposes trace ID to the Browser Overlay JS
164
+ ```
165
+
166
+ ### 3.5. Railtie Auto-Registration
167
+
168
+ The `E11y::Railtie` automatically registers the DevLog adapter in `development` and `test` environments when no `:dev_log` adapter is already configured. ENV vars control limits at boot:
169
+
170
+ ```bash
171
+ E11Y_MAX_EVENTS=5000 # override max lines
172
+ E11Y_MAX_SIZE=10485760 # override max file size (bytes)
173
+ E11Y_KEEP_ROTATED=3 # override number of rotated files kept
174
+ ```
175
+
176
+ ---
177
+
178
+ ## 4. TUI — Interactive Log Viewer
179
+
180
+ ### 4.1. Entry
181
+
182
+ ```bash
183
+ bundle exec e11y # default: launches TUI
184
+ bundle exec e11y tui # explicit
185
+ ```
186
+
187
+ ### 4.2. Three-View Navigation
188
+
189
+ The TUI presents a drill-down hierarchy:
190
+
191
+ ```
192
+ :interactions → :events → :detail
193
+ (list of (events in (full JSON
194
+ interactions) one trace) of one event)
195
+ ```
196
+
197
+ ### 4.3. Keyboard Map
198
+
199
+ | Key | View | Action |
200
+ |---|---|---|
201
+ | `↓` / `↑` | interactions, events | Navigate down/up |
202
+ | `Enter` | interactions | Drill into events for selected interaction |
203
+ | `Enter` | events | Open detail overlay |
204
+ | `Esc` / `b` | events, detail | Go back |
205
+ | `w` | interactions | Source filter: web requests only |
206
+ | `j` | interactions | Source filter: background jobs only |
207
+ | `a` | interactions | Source filter: all sources |
208
+ | `r` | interactions | Force reload |
209
+ | `q` | any | Quit |
210
+ | `c` | detail | Copy event JSON to clipboard |
211
+
212
+ ### 4.4. Interaction Grouping
213
+
214
+ The `Grouping.group` function converts a flat list of trace IDs into `Interaction` structs:
215
+
216
+ ```ruby
217
+ E11y::Devtools::Tui::Grouping.group(traces, window_ms: 500)
218
+ ```
219
+
220
+ Algorithm:
221
+ 1. Sort traces by `started_at`.
222
+ 2. Assign traces that start within `window_ms` of the group's start into the same `Interaction`.
223
+ 3. Return interactions sorted newest-first.
224
+
225
+ This converts N parallel async traces into a single human-readable row without requiring any client-side coordination (no `X-Interaction-ID` header).
226
+
227
+ ### 4.5. File Watcher
228
+
229
+ The TUI polls `File.mtime` every `POLL_INTERVAL_MS = 250` ms. No inotify/kqueue dependency — zero platform-specific code, cross-platform by default.
230
+
231
+ ### 4.6. Widgets
232
+
233
+ | Widget | Description |
234
+ |---|---|
235
+ | `InteractionList` | One row per interaction. Bullet: `●` red (has errors) / `○` gray (clean). |
236
+ | `EventList` | Table of events for a trace, colored by severity. |
237
+ | `EventDetail` | Popup overlay showing full JSON of a single event. |
238
+
239
+ ---
240
+
241
+ ## 5. Browser Overlay
242
+
243
+ ### 5.1. Rails Engine
244
+
245
+ The overlay is a Rails Engine with isolated namespace, mounted automatically at `/_e11y/`:
246
+
247
+ ```ruby
248
+ # config/routes.rb (added by Railtie)
249
+ mount E11y::Devtools::Overlay::Engine => "/_e11y"
250
+ ```
251
+
252
+ All controller actions return `404 Not Found` outside the `development` environment, making accidental production mount harmless.
253
+
254
+ ### 5.2. Controller Endpoints
255
+
256
+ | Method | Path | Description |
257
+ |---|---|---|
258
+ | `GET` | `/_e11y/events?trace_id=` | Events for a specific trace |
259
+ | `GET` | `/_e11y/events/recent?limit=` | Most recent N events |
260
+ | `DELETE` | `/_e11y/events` | Clear log; returns 204 No Content |
261
+
262
+ ### 5.3. Rack Middleware — Script Injection
263
+
264
+ `E11y::Devtools::Overlay::Middleware` sits in the Rack stack and injects the overlay script into HTML responses:
265
+
266
+ - Skips: XHR requests, asset paths (`/assets/`, `.js`, `.css`, etc.), non-HTML content types.
267
+ - Injects `<script>` tag before `</body>`.
268
+ - Injects `window.__E11Y_TRACE_ID__` with the current request's trace ID.
269
+ - Recalculates and updates `Content-Length` header.
270
+
271
+ ### 5.4. Custom Element
272
+
273
+ The injected script registers a vanilla JS Custom Element `<e11y-overlay>` using Shadow DOM:
274
+
275
+ - **Badge**: floating, bottom-right corner. Shows event count and error count.
276
+ - **Error indicator**: red border around the badge when any event in the current trace has severity `error` or `fatal`.
277
+ - **Panel**: click the badge to open a slide-in panel showing the current trace's events.
278
+ - **Polling**: queries `/_e11y/events?trace_id=...` every 2 seconds.
279
+ - **Footer actions**: `[clear log]` (DELETE) and `[copy trace_id]`.
280
+
281
+ No npm build step, no React, no webpack — the script is a single file of vanilla JS shipped with the gem.
282
+
283
+ ---
284
+
285
+ ## 6. MCP Server
286
+
287
+ ### 6.1. Entry
288
+
289
+ ```bash
290
+ bundle exec e11y mcp # stdio transport (Claude Desktop, Cursor)
291
+ bundle exec e11y mcp --port 3099 # StreamableHTTP transport (WEBrick)
292
+ ```
293
+
294
+ ### 6.2. Tools
295
+
296
+ The MCP server exposes 8 tools backed by `DevLog::Query`:
297
+
298
+ | Tool | Description |
299
+ |---|---|
300
+ | `RecentEvents` | Most recent N events (default 50) |
301
+ | `EventsByTrace` | All events for a given `trace_id` |
302
+ | `Search` | Full-text search across event JSON |
303
+ | `Stats` | Summary: total count, error rate, top event types |
304
+ | `Interactions` | Grouped interaction list (same grouping as TUI) |
305
+ | `EventDetail` | Full data for a single event by ID |
306
+ | `Errors` | All events with severity `error` or `fatal` |
307
+ | `Clear` | Truncate the log file |
308
+
309
+ ### 6.3 AI Tool Setup
310
+
311
+ **Cursor** (`.cursor/mcp.json`):
312
+
313
+ ```json
314
+ {
315
+ "mcpServers": {
316
+ "e11y": {
317
+ "command": "bundle",
318
+ "args": ["exec", "e11y", "mcp"],
319
+ "cwd": "/path/to/your/rails/app"
320
+ }
321
+ }
322
+ }
323
+ ```
324
+
325
+ **Claude Code** (`.claude/mcp.json` or `claude_desktop_config.json`):
326
+
327
+ ```json
328
+ {
329
+ "mcpServers": {
330
+ "e11y": {
331
+ "command": "bundle",
332
+ "args": ["exec", "e11y", "mcp"]
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ Once connected, ask your AI assistant: *"What errors happened in the last request?"* or *"Show me all events for trace abc-123"*.
339
+
340
+ ### 6.4. Server Context
341
+
342
+ The `server_context` passed to every tool handler contains:
343
+
344
+ ```ruby
345
+ { store: E11y::Adapters::DevLog::Query.new(log_path) }
346
+ ```
347
+
348
+ Tools call `context[:store]` directly — no shared mutable state between requests.
349
+
350
+ ---
351
+
352
+ ## 7. CLI Entry Point
353
+
354
+ The `e11y` executable (`gems/e11y-devtools/exe/e11y`) dispatches subcommands:
355
+
356
+ | Subcommand | Behavior |
357
+ |---|---|
358
+ | `e11y` (no args) | Launches TUI (default) |
359
+ | `e11y tui` | Launches TUI explicitly |
360
+ | `e11y mcp` | Starts MCP server on stdio |
361
+ | `e11y mcp --port N` | Starts MCP server on port N over HTTP |
362
+ | `e11y tail` | Streams new events to stdout (like `tail -f`) |
363
+ | `e11y help` | Prints usage |
364
+
365
+ **Log path auto-detection**: the CLI walks up from `Dir.pwd` looking for `log/e11y_dev.jsonl`, stopping at the first directory that contains it (or a `Gemfile`). This makes it work correctly from any subdirectory of the project.
366
+
367
+ ---
368
+
369
+ ## 8. Monorepo Structure
370
+
371
+ e11y uses a two-gem monorepo layout:
372
+
373
+ ```
374
+ e11y/ ← root
375
+ ├── e11y.gemspec ← production gem (v0.2.x)
376
+ ├── lib/
377
+ │ └── e11y/
378
+ │ └── adapters/
379
+ │ └── dev_log/ ← DevLog adapter (production-safe)
380
+ │ ├── file_store.rb
381
+ │ ├── query.rb
382
+ │ └── dev_log.rb
383
+ └── gems/
384
+ └── e11y-devtools/ ← separate gem (dev-only)
385
+ ├── e11y-devtools.gemspec
386
+ └── lib/
387
+ └── e11y/
388
+ └── devtools/
389
+ ├── tui/ ← TUI viewer
390
+ ├── overlay/ ← Rails Engine + Rack middleware + JS
391
+ └── mcp/ ← MCP server tools
392
+ ```
393
+
394
+ ### 8.1 Why Two Gems?
395
+
396
+ The write-path (`DevLog` adapter, `FileStore`, `Query`, `DevLogSource` middleware) is
397
+ **production-safe**: it has zero viewer dependencies and runs in any environment.
398
+ It lives in `gem 'e11y'` so production apps can enable it if needed (e.g., for
399
+ log-based observability pipelines).
400
+
401
+ The viewers (TUI via ratatui_ruby, Browser Overlay, MCP Server) are **dev-only** by
402
+ design. `ratatui_ruby` ships Rust-compiled binaries; adding it to production gems
403
+ inflates deploy size and introduces native-extension compilation. Keeping viewers in
404
+ a separate gem makes the dependency opt-in:
405
+
406
+ ```ruby
407
+ # Gemfile
408
+ gem "e11y" # always
409
+ gem "e11y-devtools", group: :development # never reaches production
410
+ ```
411
+
412
+ ### 8.2. Gem Dependencies
413
+
414
+ **`e11y.gemspec`** (production gem — no devtools dependencies):
415
+ ```ruby
416
+ # No ratatui_ruby, no mcp gem here
417
+ ```
418
+
419
+ **`gems/e11y-devtools/e11y-devtools.gemspec`** (opt-in dev gem):
420
+ ```ruby
421
+ spec.add_dependency "e11y", "~> 0.2"
422
+ spec.add_dependency "ratatui_ruby", "~> 1.4"
423
+ spec.add_dependency "mcp", ">= 1.0"
424
+ ```
425
+
426
+ Developers add `e11y-devtools` only to the `:development` group in their Gemfile:
427
+
428
+ ```ruby
429
+ gem "e11y"
430
+
431
+ group :development do
432
+ gem "e11y-devtools"
433
+ end
434
+ ```
435
+
436
+ ---
437
+
438
+ ## 9. Noise Reduction Philosophy
439
+
440
+ Local development log noise is the primary usability concern. e11y-devtools applies noise reduction at three independent layers:
441
+
442
+ ### Layer 1: Buffer Flush (inherited from e11y core)
443
+
444
+ Debug-level events accumulate in the request-scoped buffer (see ADR-001). They are written to `log/e11y_dev.jsonl` **only when the request fails**. A successful request produces zero debug-level entries in the devlog.
445
+
446
+ ### Layer 2: Viewer Defaults
447
+
448
+ | Viewer | Default filter |
449
+ |---|---|
450
+ | TUI | `:web` source filter — shows only web request interactions, not background jobs |
451
+ | Browser Overlay | Current trace only — the panel shows only events from the active request |
452
+ | MCP `recent_events` | `limit: 50` — bounded by default |
453
+
454
+ ### Layer 3: Interaction Grouping
455
+
456
+ A single user action typically spawns multiple async traces (background jobs, ActionCable, webhooks). The TUI groups traces within a 500 ms window into one `Interaction` row, reducing visual noise from N rows to 1.
457
+
458
+ The three layers are independent. Any one layer alone would reduce noise meaningfully; together they make the default view tractable even in busy development servers.
459
+
460
+ ---
461
+
462
+ ## 10. Technology Choices & Alternatives
463
+
464
+ ### 10.1. TUI Library
465
+
466
+ | Candidate | Chosen? | Rationale |
467
+ |---|---|---|
468
+ | `ratatui_ruby` | **Yes** | Built-in `TestHelper` for unit testing widgets; 44 published versions (stable); all needed widgets available; single GC (no subprocess) |
469
+ | `charm-ruby` | No | ~10 commits total at decision time; no built-in test support |
470
+ | Plain ANSI escape codes | No | Custom widget code duplication; no input handling |
471
+
472
+ ### 10.2. MCP Library
473
+
474
+ | Candidate | Chosen? | Rationale |
475
+ |---|---|---|
476
+ | `mcp` (Anthropic+Shopify) | **Yes** | StreamableHTTP transport; full MCP spec compliance; actively maintained by protocol authors |
477
+ | `fast-mcp` | No | No StreamableHTTP support at decision time; community-maintained, not spec-complete |
478
+ | Custom JSON-RPC | No | Significant maintenance surface; no transport flexibility |
479
+
480
+ ### 10.3. Trace Grouping Strategy
481
+
482
+ | Approach | Chosen? | Rationale |
483
+ |---|---|---|
484
+ | Time-window grouping (500 ms) | **Yes** | No client-side coordination; zero configuration; works without HTTP header support |
485
+ | `X-Interaction-ID` header | No | Requires all clients (background jobs, ActionCable) to propagate a custom header; breaks for third-party callers |
486
+
487
+ ### 10.4. File Watch Strategy
488
+
489
+ | Approach | Chosen? | Rationale |
490
+ |---|---|---|
491
+ | Poll `File.mtime` every 250 ms | **Yes** | Zero native dependencies; works on macOS, Linux, Docker without kernel feature flags |
492
+ | `inotify` / `kqueue` | No | Platform-specific; adds C extension dependency; Docker volume mounts may not deliver events |
493
+ | `listen` gem | No | Pulls in `rb-fsevent` / `rb-inotify`; heavy for a dev-only tool |
494
+
495
+ ### 10.5. Browser Overlay Build
496
+
497
+ | Approach | Chosen? | Rationale |
498
+ |---|---|---|
499
+ | Vanilla JS Custom Element | **Yes** | No npm build; zero-config; ships as a single `.js` file in the gem |
500
+ | React SPA | No | Requires npm build step; breaks zero-config install; 100 KB+ overhead |
501
+ | Separate dev server | No | Port management; firewall issues; second process to manage |
502
+
503
+ ---
504
+
505
+ ## 11. Trade-offs
506
+
507
+ ### 11.1. Accepted Trade-offs
508
+
509
+ **Polling instead of push**: The 250 ms poll interval means the TUI and overlay lag by up to 250 ms. This is imperceptible in practice for a developer tool and eliminates all platform-specific file-watch dependencies.
510
+
511
+ **JSONL over SQLite**: A flat JSONL file is simpler to rotate, inspect with standard tools (`tail`, `jq`), and ship without native dependencies. Random-access query performance at 10 000 events (the default limit) is acceptable with the mtime-cached in-memory parse.
512
+
513
+ **Interaction grouping is heuristic**: The 500 ms window is a heuristic — it may merge unrelated concurrent requests or split a single slow interaction. It is a viewer-level concern only; the raw JSONL contains full per-trace data for manual inspection.
514
+
515
+ **No structured query language**: The `search` method is full-text across serialized JSON. This is sufficient for local development workflows and avoids embedding a query parser.
516
+
517
+ ### 11.2. Future Considerations
518
+
519
+ - Configurable `window_ms` for interaction grouping (currently hard-coded at 500).
520
+ - WebSocket push from the overlay controller to eliminate polling lag in the browser.
521
+ - Index file alongside JSONL for O(1) trace lookup at scale (relevant if `max_lines` is raised significantly).
522
+ - `e11y tail` output formats: JSON, pretty-print, structured table.