railwatch 0.9.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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +63 -0
  4. data/README.md +51 -60
  5. data/config/routes.rb +0 -59
  6. data/docs/ai-and-mcp.md +9 -8
  7. data/docs/configuration.md +35 -66
  8. data/docs/faq.md +17 -25
  9. data/docs/getting-started.md +105 -117
  10. data/docs/replacing-nightwatch.md +8 -7
  11. data/docs/replacing-sentry.md +12 -16
  12. data/docs/security.md +14 -20
  13. data/docs/self-hosting.md +24 -22
  14. data/docs/testing.md +6 -14
  15. data/docs/troubleshooting.md +12 -46
  16. data/lib/generators/railwatch/install/install_generator.rb +12 -240
  17. data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
  18. data/lib/generators/railwatch/install/templates/post-deploy +0 -7
  19. data/lib/railwatch/authentication.rb +6 -5
  20. data/lib/railwatch/configuration.rb +18 -152
  21. data/lib/railwatch/engine.rb +9 -118
  22. data/lib/railwatch/patches.rb +0 -11
  23. data/lib/railwatch/reporter.rb +13 -20
  24. data/lib/railwatch/subscribers/process_info.rb +1 -2
  25. data/lib/railwatch/version.rb +1 -1
  26. data/lib/railwatch.rb +4 -62
  27. data/lib/tasks/railwatch_tasks.rake +3 -325
  28. data/llms.txt +23 -25
  29. metadata +3 -453
  30. data/app/channels/railwatch/environment_channel.rb +0 -62
  31. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  32. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  33. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  34. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  35. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  36. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  37. data/app/controllers/railwatch/commands_controller.rb +0 -16
  38. data/app/controllers/railwatch/comments_controller.rb +0 -11
  39. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  40. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  41. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  42. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  43. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  44. data/app/controllers/railwatch/executions_controller.rb +0 -13
  45. data/app/controllers/railwatch/issues_controller.rb +0 -258
  46. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  47. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  48. data/app/controllers/railwatch/logs_controller.rb +0 -40
  49. data/app/controllers/railwatch/mails_controller.rb +0 -19
  50. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  51. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  52. data/app/controllers/railwatch/overview_controller.rb +0 -38
  53. data/app/controllers/railwatch/people_controller.rb +0 -25
  54. data/app/controllers/railwatch/processes_controller.rb +0 -33
  55. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  56. data/app/controllers/railwatch/queries_controller.rb +0 -59
  57. data/app/controllers/railwatch/releases_controller.rb +0 -75
  58. data/app/controllers/railwatch/requests_controller.rb +0 -37
  59. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  60. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  61. data/app/controllers/railwatch/spans_controller.rb +0 -47
  62. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  63. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  64. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  65. data/app/controllers/railwatch/traces_controller.rb +0 -72
  66. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  67. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  68. data/app/controllers/railwatch/visits_controller.rb +0 -53
  69. data/app/frontend/components/app-content.tsx +0 -26
  70. data/app/frontend/components/app-logo-icon.tsx +0 -23
  71. data/app/frontend/components/app-logo.tsx +0 -14
  72. data/app/frontend/components/app-shell.tsx +0 -38
  73. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  74. data/app/frontend/components/app-sidebar.tsx +0 -94
  75. data/app/frontend/components/app-wordmark.tsx +0 -29
  76. data/app/frontend/components/breadcrumbs.tsx +0 -46
  77. data/app/frontend/components/icon.tsx +0 -16
  78. data/app/frontend/components/input-error.tsx +0 -18
  79. data/app/frontend/components/nav-footer.tsx +0 -46
  80. data/app/frontend/components/nav-main.tsx +0 -41
  81. data/app/frontend/components/nav-user.tsx +0 -50
  82. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  83. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  84. data/app/frontend/components/railwatch/code.tsx +0 -28
  85. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  86. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  87. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  88. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  89. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  90. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  91. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  92. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  93. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  94. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  95. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  96. data/app/frontend/components/railwatch/frames.tsx +0 -295
  97. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  98. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  99. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  100. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  101. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  102. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  103. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  104. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  105. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  106. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  107. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  108. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  109. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  110. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  111. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  112. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  113. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  114. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  115. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  116. data/app/frontend/components/railwatch/stat.tsx +0 -185
  117. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  118. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  119. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  120. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  121. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  122. data/app/frontend/components/ui/avatar.tsx +0 -51
  123. data/app/frontend/components/ui/badge.tsx +0 -46
  124. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  125. data/app/frontend/components/ui/button.tsx +0 -60
  126. data/app/frontend/components/ui/card.tsx +0 -92
  127. data/app/frontend/components/ui/chart.tsx +0 -374
  128. data/app/frontend/components/ui/checkbox.tsx +0 -32
  129. data/app/frontend/components/ui/collapsible.tsx +0 -31
  130. data/app/frontend/components/ui/command.tsx +0 -182
  131. data/app/frontend/components/ui/dialog.tsx +0 -158
  132. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  133. data/app/frontend/components/ui/hover-card.tsx +0 -42
  134. data/app/frontend/components/ui/input.tsx +0 -21
  135. data/app/frontend/components/ui/label.tsx +0 -24
  136. data/app/frontend/components/ui/popover.tsx +0 -87
  137. data/app/frontend/components/ui/select.tsx +0 -185
  138. data/app/frontend/components/ui/separator.tsx +0 -28
  139. data/app/frontend/components/ui/sheet.tsx +0 -137
  140. data/app/frontend/components/ui/sidebar.tsx +0 -726
  141. data/app/frontend/components/ui/skeleton.tsx +0 -13
  142. data/app/frontend/components/ui/sonner.tsx +0 -41
  143. data/app/frontend/components/ui/table.tsx +0 -114
  144. data/app/frontend/components/ui/tabs.tsx +0 -91
  145. data/app/frontend/components/ui/textarea.tsx +0 -18
  146. data/app/frontend/components/ui/tooltip.tsx +0 -61
  147. data/app/frontend/components/user-info.tsx +0 -32
  148. data/app/frontend/components/user-menu-content.tsx +0 -94
  149. data/app/frontend/entrypoints/application.css +0 -244
  150. data/app/frontend/entrypoints/inertia.tsx +0 -92
  151. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  152. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  153. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  154. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  157. data/app/frontend/hooks/use-appearance.tsx +0 -67
  158. data/app/frontend/hooks/use-clipboard.ts +0 -32
  159. data/app/frontend/hooks/use-flash.tsx +0 -23
  160. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  161. data/app/frontend/hooks/use-initials.tsx +0 -15
  162. data/app/frontend/hooks/use-live.ts +0 -205
  163. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  164. data/app/frontend/hooks/use-mobile.ts +0 -21
  165. data/app/frontend/hooks/use-percentile.ts +0 -24
  166. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  167. data/app/frontend/hooks/use-row-nav.ts +0 -106
  168. data/app/frontend/hooks/use-step.ts +0 -27
  169. data/app/frontend/hooks/use-ticker.ts +0 -12
  170. data/app/frontend/hooks/use-window.ts +0 -58
  171. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  172. data/app/frontend/layouts/app-layout.tsx +0 -21
  173. data/app/frontend/layouts/env-layout.tsx +0 -53
  174. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  175. data/app/frontend/lib/cable.ts +0 -10
  176. data/app/frontend/lib/env-pages.ts +0 -236
  177. data/app/frontend/lib/execution-path.ts +0 -55
  178. data/app/frontend/lib/explain.ts +0 -50
  179. data/app/frontend/lib/filter.ts +0 -25
  180. data/app/frontend/lib/flamegraph.ts +0 -155
  181. data/app/frontend/lib/format.ts +0 -71
  182. data/app/frontend/lib/keyboard.ts +0 -18
  183. data/app/frontend/lib/mount.ts +0 -23
  184. data/app/frontend/lib/source-link.ts +0 -217
  185. data/app/frontend/lib/utils.ts +0 -6
  186. data/app/frontend/pages/alerts/index.tsx +0 -171
  187. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  188. data/app/frontend/pages/cache_events/index.tsx +0 -127
  189. data/app/frontend/pages/commands/index.tsx +0 -143
  190. data/app/frontend/pages/deploys/index.tsx +0 -94
  191. data/app/frontend/pages/deploys/show.tsx +0 -309
  192. data/app/frontend/pages/deprecations/index.tsx +0 -176
  193. data/app/frontend/pages/exceptions/index.tsx +0 -271
  194. data/app/frontend/pages/executions/show.tsx +0 -821
  195. data/app/frontend/pages/issues/index.tsx +0 -399
  196. data/app/frontend/pages/issues/show.tsx +0 -1367
  197. data/app/frontend/pages/jobs/index.tsx +0 -381
  198. data/app/frontend/pages/jobs/klass.tsx +0 -215
  199. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  200. data/app/frontend/pages/logs/index.tsx +0 -303
  201. data/app/frontend/pages/mails/index.tsx +0 -218
  202. data/app/frontend/pages/notifications/index.tsx +0 -161
  203. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  204. data/app/frontend/pages/overview/show.tsx +0 -333
  205. data/app/frontend/pages/people/index.tsx +0 -102
  206. data/app/frontend/pages/people/show.tsx +0 -149
  207. data/app/frontend/pages/processes/index.tsx +0 -335
  208. data/app/frontend/pages/profiles/index.tsx +0 -110
  209. data/app/frontend/pages/profiles/show.tsx +0 -105
  210. data/app/frontend/pages/queries/index.tsx +0 -492
  211. data/app/frontend/pages/queries/show.tsx +0 -276
  212. data/app/frontend/pages/releases/index.tsx +0 -190
  213. data/app/frontend/pages/releases/release-health.tsx +0 -68
  214. data/app/frontend/pages/releases/release-path.ts +0 -25
  215. data/app/frontend/pages/releases/show.tsx +0 -262
  216. data/app/frontend/pages/requests/index.tsx +0 -235
  217. data/app/frontend/pages/requests/route.tsx +0 -159
  218. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  219. data/app/frontend/pages/spans/index.tsx +0 -229
  220. data/app/frontend/pages/spans/show.tsx +0 -220
  221. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  222. data/app/frontend/pages/tenants/index.tsx +0 -236
  223. data/app/frontend/pages/tenants/show.tsx +0 -477
  224. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  225. data/app/frontend/pages/thresholds/index.tsx +0 -414
  226. data/app/frontend/pages/traces/show.tsx +0 -208
  227. data/app/frontend/pages/transactions/index.tsx +0 -220
  228. data/app/frontend/pages/view_renders/index.tsx +0 -241
  229. data/app/frontend/pages/visits/index.tsx +0 -321
  230. data/app/frontend/public/icon.png +0 -0
  231. data/app/frontend/public/icon.svg +0 -5
  232. data/app/frontend/routes/index.d.ts +0 -2136
  233. data/app/frontend/routes/index.js +0 -2112
  234. data/app/frontend/types/globals.d.ts +0 -9
  235. data/app/frontend/types/index.ts +0 -288
  236. data/app/frontend/types/vite-env.d.ts +0 -9
  237. data/app/helpers/railwatch/assets_helper.rb +0 -52
  238. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  239. data/app/jobs/railwatch/application_job.rb +0 -7
  240. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  241. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  242. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  243. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  244. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  245. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  246. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  247. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  248. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  249. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  250. data/app/jobs/railwatch/rollup_job.rb +0 -132
  251. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  252. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  253. data/app/models/railwatch/alert.rb +0 -229
  254. data/app/models/railwatch/alert_rule.rb +0 -121
  255. data/app/models/railwatch/anomaly_rule.rb +0 -51
  256. data/app/models/railwatch/application.rb +0 -44
  257. data/app/models/railwatch/application_record.rb +0 -33
  258. data/app/models/railwatch/comment.rb +0 -36
  259. data/app/models/railwatch/deploy.rb +0 -67
  260. data/app/models/railwatch/environment.rb +0 -68
  261. data/app/models/railwatch/execution_presenter.rb +0 -185
  262. data/app/models/railwatch/filter_query.rb +0 -184
  263. data/app/models/railwatch/followup_receipt.rb +0 -27
  264. data/app/models/railwatch/ingest/batch.rb +0 -355
  265. data/app/models/railwatch/ingest/mapper.rb +0 -585
  266. data/app/models/railwatch/ingest/payload.rb +0 -96
  267. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  268. data/app/models/railwatch/ingest/writer.rb +0 -137
  269. data/app/models/railwatch/issue.rb +0 -277
  270. data/app/models/railwatch/issue_activity.rb +0 -23
  271. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  272. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  273. data/app/models/railwatch/maintenance_task.rb +0 -53
  274. data/app/models/railwatch/saved_view.rb +0 -63
  275. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  276. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  277. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  278. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  279. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  280. data/app/models/railwatch/telemetry/child.rb +0 -53
  281. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  282. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  283. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  284. data/app/models/railwatch/telemetry/exception.rb +0 -28
  285. data/app/models/railwatch/telemetry/execution.rb +0 -152
  286. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  287. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  288. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  289. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  290. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  291. data/app/models/railwatch/telemetry/log.rb +0 -91
  292. data/app/models/railwatch/telemetry/mail.rb +0 -13
  293. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  294. data/app/models/railwatch/telemetry/notification.rb +0 -13
  295. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  296. data/app/models/railwatch/telemetry/person.rb +0 -50
  297. data/app/models/railwatch/telemetry/process.rb +0 -10
  298. data/app/models/railwatch/telemetry/profile.rb +0 -37
  299. data/app/models/railwatch/telemetry/query.rb +0 -57
  300. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  301. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  302. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  303. data/app/models/railwatch/telemetry/session.rb +0 -24
  304. data/app/models/railwatch/telemetry/span.rb +0 -19
  305. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  306. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  307. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  308. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  309. data/app/models/railwatch/telemetry/visit.rb +0 -24
  310. data/app/models/railwatch/telemetry_record.rb +0 -107
  311. data/app/models/railwatch/threshold.rb +0 -66
  312. data/app/models/railwatch/user.rb +0 -47
  313. data/app/models/railwatch/viewer.rb +0 -13
  314. data/app/models/railwatch/window.rb +0 -66
  315. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  316. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  317. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  318. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  319. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  320. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  321. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  322. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  323. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  324. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  325. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  327. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  328. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  329. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  330. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  331. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  332. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  333. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  334. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  335. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  336. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  337. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  338. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  340. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  341. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  342. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  343. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  344. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  345. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  347. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  348. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  349. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  350. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  351. data/docs/embedded.md +0 -503
  352. data/lib/puma/plugin/railwatch.rb +0 -215
  353. data/lib/railwatch/dashboard_assets.rb +0 -45
  354. data/lib/railwatch/embedded.rb +0 -50
  355. data/lib/railwatch/export/client.rb +0 -124
  356. data/lib/railwatch/export/lease.rb +0 -56
  357. data/lib/railwatch/export/outbox.rb +0 -361
  358. data/lib/railwatch/export/policy.rb +0 -79
  359. data/lib/railwatch/export/sender.rb +0 -130
  360. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  361. data/lib/railwatch/maintenance.rb +0 -194
  362. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  363. data/lib/railwatch/transport/local.rb +0 -78
  364. data/lib/railwatch/transport/socket.rb +0 -183
  365. data/lib/railwatch/writer.rb +0 -370
  366. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  367. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  368. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  369. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  372. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  373. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  374. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  375. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  376. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  377. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  378. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  379. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  380. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  381. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  382. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  383. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  384. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  385. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  386. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  387. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  388. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  389. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  390. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  391. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  392. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  393. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  394. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  395. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  396. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  397. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  398. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  399. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  400. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  401. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  402. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  403. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  404. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  405. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  406. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  407. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  408. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  409. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  410. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  411. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  412. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  413. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  414. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  415. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  416. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  417. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  418. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  419. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  420. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  421. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  422. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  423. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  424. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  425. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  426. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  427. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  428. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  429. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  430. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  431. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  432. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  433. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  434. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  435. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  436. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  437. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  438. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  439. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  440. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  441. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  442. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  443. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  444. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  445. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  446. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  447. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  448. data/public/railwatch/icon.png +0 -0
  449. data/public/railwatch/icon.svg +0 -5
  450. data/public/railwatch/manifest.json +0 -1245
  451. data/public/railwatch/rails-vite.json +0 -1
  452. /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
data/lib/railwatch.rb CHANGED
@@ -21,19 +21,9 @@ require "railwatch/current"
21
21
  require "railwatch/record"
22
22
  require "railwatch/buffer"
23
23
  require "railwatch/transport/wire_encoder"
24
- require "railwatch/export/policy"
25
- require "railwatch/export/lease"
26
- require "railwatch/export/outbox"
27
- require "railwatch/export/client"
28
- require "railwatch/export/sender"
29
24
  require "railwatch/transport/http"
30
- require "railwatch/transport/local"
31
- require "railwatch/transport/socket"
32
- require "railwatch/writer"
33
25
  require "railwatch/authentication"
34
26
  require "railwatch/json_compat"
35
- require "railwatch/embedded"
36
- require "railwatch/ingest_request_body_limit"
37
27
  require "railwatch/reporter"
38
28
  require "railwatch/sampler"
39
29
  require "railwatch/redactor"
@@ -69,7 +59,7 @@ module Railwatch
69
59
  end
70
60
 
71
61
  def reporter
72
- @reporter ||= Reporter.new(config, transport: local_transport)
62
+ @reporter ||= Reporter.new(config)
73
63
  end
74
64
 
75
65
  # Called once config/initializers has run, so a reporter built before
@@ -87,16 +77,6 @@ module Railwatch
87
77
  @reporter&.adopt_final_config!
88
78
  end
89
79
 
90
- # Embedded mode: a Puma worker hands its batches to the writer process
91
- # over the socket; the writer itself, and any process when no socket is
92
- # configured, writes them straight into SQLite. nil means HTTP.
93
- def local_transport
94
- return nil unless config.local?
95
- return Transport::Local.new(config) if Writer.running? || config.writer_socket_path.nil?
96
-
97
- Transport::Socket.new(config)
98
- end
99
-
100
80
  def redactor
101
81
  @redactor ||= Redactor.new(config)
102
82
  end
@@ -124,9 +104,7 @@ module Railwatch
124
104
  Subscribers::Users.restart_after_fork!
125
105
  Subscribers::ProcessInfo.restart_after_fork!
126
106
  Health.restart_after_fork!
127
- Export::Sender.restart_after_fork!
128
107
  Sessions.restart_after_fork!
129
- Maintenance.restart_after_fork!
130
108
  end
131
109
 
132
110
  # --- execution lifecycle -------------------------------------------------
@@ -619,45 +597,16 @@ module Railwatch
619
597
  end
620
598
 
621
599
  require "railwatch/subscribers"
622
- require "railwatch/dashboard_assets"
623
600
 
624
601
  module Railwatch
625
- # Where the engine's two databases migrate from. database.yml names them
626
- # (`migrations_paths: <%= Railwatch.migrations_path(:railwatch) %>`) so a
627
- # host's db:prepare creates the tables on install and migrates them after
628
- # every gem update, from the gem's own history, the way Active Storage
629
- # migrates its tables. Nothing is copied into the app.
630
- def self.migrations_path(database)
631
- File.expand_path("../db/#{database}_migrate", __dir__)
632
- end
633
-
634
- # Where links the models build (a trace from an execution page, a saved
635
- # view's page) resolve. The engine's own routes serve the embedded
636
- # dashboard; a host that mounts these pages on its own routes, as
637
- # Railwatch Cloud does, points this at them.
638
- def self.url_helpers
639
- @url_helpers || Engine.routes.url_helpers
640
- end
641
-
642
- def self.url_helpers=(helpers)
643
- @url_helpers = helpers
644
- end
645
-
646
- # The one browser client, relative to the gem root. The dashboard bundle
647
- # is built from it and the install generator copies it into a host.
648
- BROWSER_CLIENT = "app/frontend/lib/railwatch.ts"
602
+ # The Inertia browser client, relative to the gem root. The install
603
+ # generator copies it into a host's app/frontend/lib.
604
+ BROWSER_CLIENT = "lib/generators/railwatch/install/templates/railwatch.ts"
649
605
 
650
606
  def self.browser_client_path
651
607
  File.expand_path("../#{BROWSER_CLIENT}", __dir__)
652
608
  end
653
609
 
654
- # The dashboard's frontend source. Railwatch Cloud builds its bundle from
655
- # this directory plus its own hosting pages, so there is one copy of every
656
- # dashboard page and component.
657
- def self.frontend_path
658
- File.expand_path("../app/frontend", __dir__)
659
- end
660
-
661
610
  # One realistic wire record per record type, exactly as the gem's own
662
611
  # subscribers emit it, generated by spec/railwatch/wire_fixtures_spec.rb.
663
612
  # A receiver tests its mapper against these instead of hand-writing what
@@ -669,12 +618,5 @@ module Railwatch
669
618
  def self.wire_fixtures
670
619
  @wire_fixtures ||= JSON.parse(File.read(wire_fixtures_path)).freeze
671
620
  end
672
-
673
- # Raised when the engine's models are used in an app whose database.yml
674
- # has no entry for them. They must never fall back to the host's primary
675
- # connection: the telemetry tables are unprefixed (`sessions`, `visits`,
676
- # `people`, `notifications`, `logs`), so a query there would read, and a
677
- # write would corrupt, the application's own tables.
678
- class DatabaseNotConfigured < StandardError; end
679
621
  end
680
622
  require "railwatch/engine" if defined?(Rails::Engine)
@@ -28,130 +28,6 @@ module Railwatch
28
28
  end
29
29
  end
30
30
 
31
- # What the embedded telemetry database is costing on disk, and the one-time
32
- # conversion that lets pruning give that cost back.
33
- #
34
- # Deleting rows does not shrink a SQLite file. The pages go on the freelist
35
- # and are reused by later inserts, and the only way back to the filesystem
36
- # is auto_vacuum=incremental plus PRAGMA incremental_vacuum -- which
37
- # PruneTelemetryJob now runs after every prune. A database can only enter
38
- # that mode while it is still empty (EnableIncrementalVacuum does that for
39
- # every telemetry database created since it shipped) or through a full
40
- # VACUUM, which is what this offers to an older one.
41
- class TelemetryDisk
42
- # Deliberately pessimistic, and said out loud as an estimate: VACUUM
43
- # rewrites the whole file, and what that costs is the operator's disk,
44
- # not ours to know.
45
- VACUUM_BYTES_PER_SECOND = 50 * 1024 * 1024
46
-
47
- # nil, having said why, for an install with no telemetry database to
48
- # talk about.
49
- def self.open
50
- unless Railwatch.config.local?
51
- puts "Railwatch reports over HTTP here (transport = :http), so this app has no telemetry database; nothing to vacuum."
52
- return nil
53
- end
54
-
55
- environment = Railwatch::Environment.current
56
- return new(environment) if environment.with_telemetry { Railwatch::TelemetryRecord.sqlite? }
57
-
58
- puts "The railwatch_telemetry database is not SQLite; auto_vacuum does not apply."
59
- nil
60
- end
61
-
62
- def initialize(environment)
63
- @environment = environment
64
- end
65
-
66
- def report
67
- with_telemetry do
68
- <<~TEXT.chomp
69
- Railwatch telemetry database
70
- file #{path}
71
- size #{human(file_bytes)}#{" + #{human(wal_bytes)} WAL" if wal_bytes.positive?}
72
- mode auto_vacuum=#{mode}
73
- free #{freelist} pages (#{human(freelist * page_size)}) on the freelist
74
- TEXT
75
- end
76
- end
77
-
78
- def advice
79
- with_telemetry do
80
- if mode != :incremental
81
- "\nPruning cannot return space to the filesystem in this mode: every page it frees stays in this file.\n" \
82
- "`bin/rails railwatch:vacuum` converts the database to incremental auto-vacuum with a full VACUUM. " \
83
- "That rewrites all #{human(file_bytes)}, needs about that much free disk for the temporary copy, and " \
84
- "holds the write lock for roughly #{estimate}. Telemetry written while it runs waits for it."
85
- elsif freelist.positive?
86
- "\nThe nightly prune returns up to #{human(Railwatch::PruneTelemetryJob::VACUUM_PAGES_PER_SLICE * Railwatch::PruneTelemetryJob::VACUUM_SLICES * page_size)} " \
87
- "a night on its own. `bin/rails railwatch:vacuum` returns all #{human(freelist * page_size)} now."
88
- else
89
- "\nNothing on the freelist: pruning is already returning this database's space as it goes."
90
- end
91
- end
92
- end
93
-
94
- # The whole point of asking explicitly, so this one is not bounded the way
95
- # the nightly prune is.
96
- def reclaim!
97
- with_telemetry do
98
- before = disk_bytes
99
- if mode == :incremental
100
- Railwatch::TelemetryRecord.incremental_vacuum
101
- else
102
- puts "\nConverting to incremental auto-vacuum (full VACUUM, roughly #{estimate})..."
103
- connection.execute("PRAGMA auto_vacuum = incremental")
104
- connection.execute("VACUUM")
105
- end
106
- # A checkpoint that cannot finish says so in its result rather than
107
- # raising: a reader still on an older snapshot holds the WAL open.
108
- # Counting only the main file would then report the WAL's bytes as
109
- # returned while they are still on the disk.
110
- busy = checkpoint_busy?
111
- [
112
- "\nauto_vacuum=#{mode}, #{human(disk_bytes)} on disk including the WAL " \
113
- "(#{human(before - disk_bytes)} returned), #{freelist} pages left on the freelist.",
114
- busy ? "The WAL could not be truncated yet -- something is still reading it. " \
115
- "Its bytes come back at the next checkpoint; run this again if you want to watch it." : nil
116
- ].compact.join("\n")
117
- end
118
- end
119
-
120
- private
121
- def with_telemetry(&) = @environment.with_telemetry(&)
122
-
123
- def connection = Railwatch::TelemetryRecord.connection
124
-
125
- def mode = Railwatch::TelemetryRecord.auto_vacuum_mode
126
-
127
- def freelist = Railwatch::TelemetryRecord.freelist_pages
128
-
129
- def page_size = Railwatch::TelemetryRecord.page_size
130
-
131
- def path = Rails.root.join(Railwatch::TelemetryRecord.connection_db_config.database.to_s)
132
-
133
- def file_bytes = File.exist?(path) ? File.size(path) : 0
134
-
135
- # What this database actually occupies. In WAL mode the pages a VACUUM
136
- # frees are not off the disk until the WAL is checkpointed, so the main
137
- # file alone understates it -- and, right after a vacuum, flatters it.
138
- def disk_bytes = file_bytes + wal_bytes
139
-
140
- # `PRAGMA wal_checkpoint` answers with [busy, log_pages, checkpointed];
141
- # a non-zero first column means it gave up rather than failed.
142
- def checkpoint_busy?
143
- connection.select_rows("PRAGMA wal_checkpoint(TRUNCATE)").dig(0, 0).to_i != 0
144
- rescue StandardError
145
- false
146
- end
147
-
148
- def wal_bytes = File.exist?("#{path}-wal") ? File.size("#{path}-wal") : 0
149
-
150
- def estimate = ActiveSupport::Duration.build([ (file_bytes / VACUUM_BYTES_PER_SECOND.to_f).ceil, 1 ].max).inspect
151
-
152
- def human(bytes) = ActiveSupport::NumberHelper.number_to_human_size(bytes)
153
- end
154
-
155
31
  # Where this install's platform lives, derived from config.ingest_url:
156
32
  # railwatch:token and railwatch:mcp point at the same host the gem already
157
33
  # ships to, so a self-hosted app never gets told to visit railwatch.rebulk.com.
@@ -189,10 +65,6 @@ namespace :railwatch do
189
65
 
190
66
  desc "Check that the app can reach Railwatch with the configured token"
191
67
  task status: :environment do
192
- if Railwatch.config.local?
193
- puts "Railwatch OK: embedded (telemetry in this app's railwatch_telemetry database; dashboard at /railwatch)"
194
- next
195
- end
196
68
  unless Railwatch.config.token.present?
197
69
  abort "RAILWATCH_TOKEN is not set"
198
70
  end
@@ -214,146 +86,6 @@ namespace :railwatch do
214
86
  ok
215
87
  end
216
88
 
217
- if config.local?
218
- # Embedded: no token and no ingest host. What can go wrong instead is
219
- # the two databases the engine writes to and the jobs that derive
220
- # rollups and issues from them.
221
- check.call(true, "transport", "local (telemetry stays in this app; dashboard at the engine mount)")
222
- # Mirroring is opt-in, so silence means "not asked for". Asked for and
223
- # not working is the case worth failing on: the operator believes their
224
- # telemetry is leaving the box and it is not.
225
- if config.export_enabled
226
- problem = config.export_problem
227
- check.call(problem.nil?, "export", problem || "mirroring to #{config.resolved_export_url}", fatal: true)
228
- if problem.nil?
229
- status = Railwatch::Export::Outbox.new(config, Railwatch::Environment.current)
230
- .then { |outbox| Railwatch::Environment.current.with_telemetry { outbox.status } }
231
- check.call(status[:state] == "ready", "export destination",
232
- status[:state] == "ready" ? "#{status[:queued_deliveries]} queued (#{status[:queued_bytes]} bytes), producer #{status[:producer_id]}" : "#{status[:state]}: #{status[:reason]} (bin/rails railwatch:export:rebind to clear)")
233
- end
234
- end
235
- %w[railwatch railwatch_telemetry].each do |name|
236
- configured = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
237
- check.call(!configured.nil?, "#{name} database",
238
- configured ? configured.database : "not in config/database.yml (bin/rails generate railwatch:install)",
239
- fatal: true)
240
- end
241
- { "railwatch_telemetry" => Railwatch::TelemetryRecord, "railwatch" => Railwatch::ApplicationRecord }.each do |name, base|
242
- pending = begin
243
- base.connection_pool.migration_context.open.pending_migrations.map(&:version)
244
- rescue StandardError => e
245
- e.message
246
- end
247
- check.call(pending == [], "#{name} migrations",
248
- case pending
249
- when [] then "up to date"
250
- when Array then "#{pending.size} pending (bin/rails db:prepare)"
251
- else "cannot check: #{pending}"
252
- end, fatal: true)
253
- end
254
- # An install created before 0.3.5 is in auto_vacuum=none, where the
255
- # nightly prune's reclaim is a silent no-op and the file only grows.
256
- # Nothing converts it on its own, because that needs a full VACUUM with
257
- # the write lock held -- so the check people actually run is where it
258
- # has to be said, rather than leaving them to notice the disk.
259
- if Railwatch.config.local? && Railwatch::TelemetryRecord.sqlite?
260
- vacuum_mode = begin
261
- Railwatch::Environment.current.with_telemetry { Railwatch::TelemetryRecord.auto_vacuum_mode }
262
- rescue StandardError => e
263
- e.message
264
- end
265
- check.call(vacuum_mode == :incremental, "telemetry disk",
266
- case vacuum_mode
267
- when :incremental then "auto_vacuum=incremental; the nightly prune returns freed pages"
268
- when Symbol then "auto_vacuum=#{vacuum_mode}: pruned pages stay in the file " \
269
- "(bin/rails railwatch:vacuum:status)"
270
- else "cannot check: #{vacuum_mode}"
271
- end)
272
- end
273
- # The maintenance clock runs in web and worker processes, not in this
274
- # rake process, so what can be checked here is whether one has ticked.
275
- last_tick = begin
276
- Railwatch::MaintenanceTask.last_tick_at
277
- rescue StandardError
278
- nil
279
- end
280
- check.call(last_tick && last_tick > 10.minutes.ago, "maintenance",
281
- if last_tick
282
- "last tick #{ActiveSupport::Duration.build((Time.current - last_tick).round).inspect} ago"
283
- else
284
- "no tick recorded yet (runs inside the app's web and worker processes, not in rake)"
285
- end)
286
- socket_path = config.writer_socket_path
287
- if socket_path && !Railwatch::Writer.usable_path?(socket_path)
288
- check.call(false, "writer process",
289
- "socket path is #{socket_path.bytesize} bytes, over Linux's #{Railwatch::Writer::MAX_SOCKET_PATH}; " \
290
- "set RAILWATCH_WRITER_SOCKET to a shorter path (e.g. /tmp/#{Rails.application.class.module_parent_name.parameterize}-railwatch.sock)")
291
- elsif socket_path
292
- listening = Railwatch::Writer.listening?(socket_path)
293
- puma_rb = Rails.root.join("config/puma.rb")
294
- plugged = puma_rb.exist? && puma_rb.read.include?("plugin :railwatch")
295
- # Not listening is only fine when the app is not running: this task
296
- # is its own process and cannot tell, so it says which case it is
297
- # looking at instead of calling a missing writer a pass.
298
- check.call(listening, "writer process",
299
- if listening
300
- "listening at #{socket_path}"
301
- elsif plugged
302
- "not listening at #{socket_path} right now. Expected if the app is stopped; if it is serving, " \
303
- "the writer died or `plugin :railwatch` never activated (check the Puma log)"
304
- else
305
- "not configured: add `plugin :railwatch` to config/puma.rb so batches are written outside the " \
306
- "processes serving requests"
307
- end)
308
- # A socket that answers is a process that is alive; the ledger is
309
- # what says it is doing its job. Nothing written in a while with the
310
- # app serving traffic is a writer that is stuck or a worker that is
311
- # not reaching it.
312
- if listening
313
- last_write = begin
314
- Railwatch::Environment.current.with_telemetry { Railwatch::Telemetry::IngestBatch.maximum(:received_at) }
315
- rescue StandardError
316
- nil
317
- end
318
- check.call(last_write && last_write > 5.minutes.ago, "last write",
319
- if last_write
320
- "#{ActiveSupport::Duration.build((Time.current - last_write).round).inspect} ago"
321
- else
322
- "no batch written yet"
323
- end)
324
- end
325
- end
326
- # HTTP Basic is on and closed until credentials exist; a deploy that
327
- # forgot gets a 401, not a public page, and this says so first.
328
- gate = config.dashboard_gate
329
- check.call(gate != :undeclared && !(gate == :basic && !config.http_basic_auth_configured? && !config.http_basic_auth_waived?),
330
- "dashboard access",
331
- case gate
332
- when :basic
333
- if config.http_basic_auth_configured?
334
- "HTTP Basic, user #{config.http_basic_auth_user}"
335
- elsif config.http_basic_auth_waived?
336
- "open in development. Production stays closed until you run " \
337
- "`RAILS_ENV=production bin/rails railwatch:authentication:configure`"
338
- else
339
- "closed: HTTP Basic is on with no credentials, so every dashboard request is 401. " \
340
- "Run `bin/rails railwatch:authentication:configure`"
341
- end
342
- when :controller then "your own: c.base_controller_class = #{config.base_controller_class}"
343
- when :resolver then "your own: c.dashboard_user decides, and live updates follow it"
344
- when :open then "deliberately open: anyone who can reach the mount can read it (c.dashboard_open)"
345
- else
346
- "undeclared: HTTP Basic is off and no base_controller_class, dashboard_user or dashboard_open " \
347
- "is set. If a routes constraint gates the mount, say so with `c.dashboard_open = true` " \
348
- "(it also enables live updates); otherwise the dashboard is public"
349
- end)
350
- check.call(!Railwatch::JsonCompat.broken?, "json compatibility",
351
- Railwatch::JsonCompat.broken? ? Railwatch::JsonCompat.advice : "json #{Railwatch::JsonCompat.json_version} decodes on Rails #{Rails.version}")
352
- recurring = Rails.root.join("config/recurring.yml")
353
- leftover = recurring.exist? && recurring.read.include?("Railwatch::")
354
- check.call(!leftover, "recurring.yml",
355
- leftover ? "Railwatch::* jobs listed in config/recurring.yml are no longer needed in embedded mode; remove them" : "no Railwatch entries (none needed)")
356
- else
357
89
  token = config.token.to_s
358
90
  check.call(!token.empty?, "token",
359
91
  token.empty? ? "RAILWATCH_TOKEN is not set" : Railwatch::SecretSafety.token_preview(token),
@@ -380,7 +112,6 @@ namespace :railwatch do
380
112
 
381
113
  check.call(Railwatch::Transport::Http.new(config).ping, "ingest reachable",
382
114
  "GET #{URI.join(config.ingest_url, '/ingest/ping')}", fatal: true)
383
- end
384
115
 
385
116
  middleware = Rails.application.middleware.map(&:name)
386
117
  position = middleware.index("Railwatch::Middleware::Request")
@@ -447,47 +178,6 @@ namespace :railwatch do
447
178
  puts "\nRailwatch is wired up."
448
179
  end
449
180
 
450
- # Disk. `railwatch:vacuum:status` only reads; `railwatch:vacuum` is the
451
- # one-time conversion a database created before EnableIncrementalVacuum
452
- # needs, and it is a rake task rather than anything automatic because it
453
- # rewrites the whole file with the write lock held.
454
- desc "Report the telemetry database's size, free pages, and whether pruning can return them to the filesystem"
455
- task "vacuum:status" => :environment do
456
- disk = Railwatch::TelemetryDisk.open or next
457
- puts disk.report
458
- puts disk.advice
459
- end
460
-
461
- desc "Reclaim disk from the telemetry database. Converts it to incremental auto-vacuum if needed -- a full VACUUM, which locks the file"
462
- task vacuum: :environment do
463
- disk = Railwatch::TelemetryDisk.open or next
464
- puts disk.report
465
- puts disk.reclaim!
466
- end
467
-
468
- namespace :export do
469
- desc "Show what the export queue is holding and whether it can send"
470
- task status: :environment do
471
- env = Railwatch::Environment.current
472
- status = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).status }
473
- status.each { |key, value| puts "#{key}: #{value}" }
474
- end
475
-
476
- desc "Clear a credential block, abandoning work admitted under the old token"
477
- task rebind: :environment do
478
- env = Railwatch::Environment.current
479
- discarded = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).rebind! }
480
- puts discarded ? "rebound; #{discarded} queued deliveries abandoned" : "export is not configured"
481
- end
482
-
483
- desc "Abandon everything queued for export without contacting the receiver"
484
- task discard: :environment do
485
- env = Railwatch::Environment.current
486
- count = env.with_telemetry { Railwatch::Export::Outbox.new(Railwatch.config, env).discard_all! }
487
- puts "#{count} queued deliveries abandoned"
488
- end
489
- end
490
-
491
181
  desc "Print where to create an ingest token for this app's Railwatch platform"
492
182
  task token: :environment do
493
183
  base = Railwatch::Endpoints.new(Railwatch.config)
@@ -583,9 +273,10 @@ namespace :railwatch do
583
273
 
584
274
  # The same shape as `mission_control:jobs:authentication:configure`: asks
585
275
  # for a user and password and writes them to the current environment's
586
- # credentials under `railwatch:`, which the engine reads at boot.
276
+ # credentials under `railwatch:`, which the engine reads at boot and
277
+ # railwatch-server's dashboard proxy checks.
587
278
  # RAILS_ENV=production bin/rails railwatch:authentication:configure
588
- desc "Configure HTTP Basic authentication for the embedded dashboard (writes Rails credentials)"
279
+ desc "Configure HTTP Basic authentication for railwatch-server's dashboard (writes Rails credentials)"
589
280
  task "authentication:configure" => :environment do
590
281
  Railwatch::Authentication.configure
591
282
  end
@@ -593,19 +284,6 @@ namespace :railwatch do
593
284
  desc "Send deploy metadata to Railwatch: rake railwatch:deploy[ref,name,url]"
594
285
  task :deploy, [ :ref, :name, :url ] => :environment do |_t, args|
595
286
  deploy = Railwatch.config.deploy or abort "RAILWATCH_DEPLOY (or KAMAL_VERSION) is not set"
596
- if Railwatch.config.local?
597
- # Embedded: the deploy marker is a row in this app's railwatch database.
598
- row = Railwatch::Environment.current.deploys.find_or_initialize_by(deploy: deploy.to_s.first(128))
599
- row.assign_attributes(ref: (args[:ref] || `git rev-parse HEAD 2>/dev/null`.strip).presence&.first(128),
600
- name: args[:name].presence&.first(255), url: args[:url].presence&.first(1024),
601
- server: Railwatch.config.server, deployed_at: Time.current,
602
- commits: Railwatch::DeployMetadata.commits,
603
- detail: { performer: ENV["KAMAL_PERFORMER"], destination: ENV["KAMAL_DESTINATION"],
604
- service: ENV["KAMAL_SERVICE"] }.compact)
605
- row.save!
606
- puts "Deploy #{deploy} recorded"
607
- next
608
- end
609
287
  abort "Plain HTTP ingest is disabled; use HTTPS or set RAILWATCH_ALLOW_HTTP=true" unless Railwatch.config.ingest_url_allowed?
610
288
  uri = URI.join(Railwatch.config.ingest_url, "/ingest/deploys")
611
289
  req = Net::HTTP::Post.new(uri)
data/llms.txt CHANGED
@@ -1,44 +1,42 @@
1
1
  # Railwatch
2
2
 
3
- > Railwatch is a Ruby gem that instruments a Rails application end to end —
4
- > requests, jobs, scheduled tasks, rake/runner commands, database queries and
5
- > N+1s, exceptions, cache, mail, notifications, broadcasts, outgoing HTTP,
6
- > Active Storage, view renders, RubyLLM calls, and logs — links every one of
7
- > them into a single execution tree by `execution_id`/`trace_id`, and writes
8
- > them from a background thread into two SQLite files the app owns (embedded,
9
- > the default) or to Railwatch Cloud. It replaces a separate APM and a
10
- > separate error tracker with one gem and one configuration block, costs
11
- > under a millisecond of CPU per request, and never writes to the
12
- > application's primary database. Install is `bundle add railwatch` followed
13
- > by `bin/rails generate railwatch:install`, which by default keeps
14
- > everything in the app (two SQLite databases, a Puma writer process, the
15
- > dashboard at `/railwatch`, open in development and password-protected
16
- > elsewhere); with `--cloud` or a token option it sends to Railwatch Cloud
17
- > instead, and an embedded install can export to the cloud as well. It
18
- > writes the initializer, mounts the engine, adds a Kamal post-deploy hook
19
- > and the Inertia browser client where the app has them, and wires the test
3
+ > Railwatch is a Ruby gem (the agent) that instruments a Rails application end
4
+ > to end — requests, jobs, scheduled tasks, rake/runner commands, database
5
+ > queries and N+1s, exceptions, cache, mail, notifications, broadcasts,
6
+ > outgoing HTTP, Active Storage, view renders, RubyLLM calls, and logs — links
7
+ > every one of them into a single execution tree by `execution_id`/`trace_id`,
8
+ > and sends them from a background thread over HTTPS to a Railwatch server:
9
+ > Railwatch Cloud (the default), a self-hosted railwatch-rs, or the one the
10
+ > separate `railwatch-server` gem runs next to Puma for an embedded dashboard
11
+ > at `/railwatch`. It replaces a separate APM and a separate error tracker
12
+ > with one gem and one configuration block, costs under a millisecond of CPU
13
+ > per request, and never writes to the application's primary database.
14
+ > Install is `bundle add railwatch` followed by
15
+ > `bin/rails generate railwatch:install` (`--prompt-token`, `--url` for a
16
+ > self-hosted server, `--kamal-secrets`), which writes the initializer,
17
+ > mounts the engine for the browser beacon, adds a Kamal post-deploy hook and
18
+ > the Inertia browser client where the app has them, and wires the test
20
19
  > matchers. `bin/rails railwatch:doctor` prints a ✓/✗ line for every piece.
21
20
 
22
21
  ## Docs
23
22
 
24
- - [README](README.md): what Railwatch is, the install, embedded mode, and where each document fits.
25
- - [Getting started](docs/getting-started.md): the two-command embedded install, the Railwatch Cloud install and where its token comes from, the three optional lines, and deploying under Kamal, Docker/Heroku/Render, or no deploy tool at all.
26
- - [Embedded mode](docs/embedded.md): the default install — the dashboard inside the app, its two SQLite databases, dashboard authentication, the Puma writer process, maintenance without a job worker, disk reclaim, and optional export to Railwatch Cloud.
27
- - [Configuration](docs/configuration.md): every configuration option and its `RAILWATCH_*` environment variable, plus the full public facade, redaction, rejection, transport and buffering behaviour, and the rake tasks.
23
+ - [README](README.md): what Railwatch is, where telemetry goes (Cloud, self-hosted, railwatch-server), the install, and where each document fits.
24
+ - [Getting started](docs/getting-started.md): the install and where its token comes from, embedded with the `railwatch-server` gem, moving from 0.x embedded mode, the three optional lines, and deploying under Kamal, Docker/Heroku/Render, or no deploy tool at all.
25
+ - [Configuration](docs/configuration.md): every configuration option and its `RAILWATCH_*` environment variable, plus the full public facade, redaction, rejection, transport and buffering behaviour, the dashboard gate railwatch-server uses, and the rake tasks.
28
26
  - [Record types](docs/records.md): every record type the gem ships and every attribute on it, including RubyLLM calls (tokens, cost, finish reason, tool calls, workflows), sourced from the code that builds it.
29
27
  - [Testing](docs/testing.md): the RSpec and Minitest matchers (`have_railwatch_queries`, `have_railwatch_n_plus_one`, `record_railwatch_span`, `record_railwatch_exception`, `have_railwatch_outgoing_requests`, `have_railwatch_http_in_transaction`) and a CI performance-gate recipe.
30
28
  - [Production source maps](docs/source-maps.md): hidden Vite maps, private upload before publishing assets, safe opt-in deletion, resolved browser stacks and default issue grouping.
31
- - [AI assistants and MCP](docs/ai-and-mcp.md): Railwatch Cloud's MCP endpoint, how to get a token, paste-ready client configuration for Claude Code, Claude Desktop, Cursor, VS Code, and Zed, and every tool, prompt, and resource the server exposes.
29
+ - [AI assistants and MCP](docs/ai-and-mcp.md): the server's MCP endpoint, how to get a token, paste-ready client configuration for Claude Code, Claude Desktop, Cursor, VS Code, and Zed, and every tool, prompt, and resource the server exposes.
32
30
  - [Replacing Sentry](docs/replacing-sentry.md): a step-by-step migration — removing the gems, porting each option, rewriting each call site, breadcrumbs, spans, profiling, attachments, `before_send`, fingerprints, and release health.
33
31
  - [Coming from Laravel Nightwatch](docs/replacing-nightwatch.md): the record-type mapping, what "execution" means in Railwatch, sampling parity, and the facade method names in Ruby.
34
- - [Self-hosting](docs/self-hosting.md): pointing the gem at a self-hosted Railwatch Cloud, creating a token there, and checking the connection.
35
- - [Troubleshooting](docs/troubleshooting.md): every `railwatch:doctor` line, embedded and cloud, and the failure modes behind them — no records, doubled scheduled tasks, WebMock in specs, Puma fork, tail-sampling memory, missing profiler, missing deploy marker, Kamal hook, log search, empty tenants.
32
+ - [Self-hosting](docs/self-hosting.md): pointing the gem at a self-hosted railwatch-rs, creating a token there, and checking the connection.
33
+ - [Troubleshooting](docs/troubleshooting.md): every `railwatch:doctor` line and the failure modes behind them — no records, doubled scheduled tasks, WebMock in specs, Puma fork, tail-sampling memory, missing profiler, missing deploy marker, Kamal hook, log search, empty tenants.
36
34
  - [Security](docs/security.md): transport, capture defaults, the browser beacon, token handling, and application responsibilities.
37
35
  - [FAQ](docs/faq.md): overhead numbers and how they are measured, retention, what is redacted by default versus opt-in, SQLite, and what happens when the platform is unreachable.
38
36
 
39
37
  ## For coding agents
40
38
 
41
- - [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the Railwatch Cloud MCP hookup.
39
+ - [AGENTS.md](AGENTS.md): how to install and use Railwatch from inside a Rails app — the facade methods, the spec matchers, `railwatch:doctor`, and the MCP hookup.
42
40
 
43
41
  ## Optional
44
42