railwatch 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +42 -26
  3. data/CHANGELOG.md +28 -0
  4. data/README.md +40 -29
  5. data/app/jobs/railwatch/rollup_job.rb +3 -1
  6. data/app/models/railwatch/ingest/rollup_absorber.rb +1 -1
  7. data/docs/ai-and-mcp.md +9 -3
  8. data/docs/configuration.md +68 -27
  9. data/docs/embedded.md +76 -43
  10. data/docs/faq.md +22 -10
  11. data/docs/getting-started.md +116 -41
  12. data/docs/records.md +13 -10
  13. data/docs/replacing-nightwatch.md +16 -14
  14. data/docs/replacing-sentry.md +19 -10
  15. data/docs/security.md +24 -3
  16. data/docs/self-hosting.md +9 -1
  17. data/docs/testing.md +14 -4
  18. data/docs/troubleshooting.md +62 -23
  19. data/lib/generators/railwatch/install/install_generator.rb +5 -4
  20. data/lib/generators/railwatch/install/templates/initializer.rb.tt +2 -2
  21. data/lib/railwatch/version.rb +1 -1
  22. data/llms.txt +21 -18
  23. data/public/railwatch/assets/{app-layout-2xAKndVD.js → app-layout-Zc0v-hYh.js} +1 -1
  24. data/public/railwatch/assets/{app-wordmark-Bt6XqURo.js → app-wordmark-BA_60AVb.js} +1 -1
  25. data/public/railwatch/assets/{appearance-DyCfeh9o.js → appearance-CcfP9tZ7.js} +1 -1
  26. data/public/railwatch/assets/application-BrN3Sz94.css +1 -0
  27. data/public/railwatch/assets/{arrow-up-EmePsTKP.js → arrow-up-CLQ-7heQ.js} +1 -1
  28. data/public/railwatch/assets/{auth-layout-BuwogWMZ.js → auth-layout-C05gEBIQ.js} +1 -1
  29. data/public/railwatch/assets/{badge-Dv7I-ebP.js → badge-DRae8XwK.js} +1 -1
  30. data/public/railwatch/assets/{braces-iZ7MEfYP.js → braces-rSYydpLY.js} +1 -1
  31. data/public/railwatch/assets/{card-DqdEEKcJ.js → card-DPjFKfen.js} +1 -1
  32. data/public/railwatch/assets/{chart-C4k7Ub9W.js → chart-DWh7l8yM.js} +1 -1
  33. data/public/railwatch/assets/{chart-hover-2yZG3w2u.js → chart-hover-CfoZUY4J.js} +1 -1
  34. data/public/railwatch/assets/{chart-panel-CONkZcn9.js → chart-panel-CC49WTQL.js} +1 -1
  35. data/public/railwatch/assets/{checkbox-DKbThbxr.js → checkbox-DPkLUiwM.js} +1 -1
  36. data/public/railwatch/assets/{code-CYahYQHZ.js → code-CLmYS6FU.js} +1 -1
  37. data/public/railwatch/assets/{copy-block-CbJum9s8.js → copy-block-CGcXxp8J.js} +1 -1
  38. data/public/railwatch/assets/{copy-id-JmvLfkmx.js → copy-id-vBYHQwxg.js} +1 -1
  39. data/public/railwatch/assets/{cursor-load-more-C1ylbqqL.js → cursor-load-more-Ddwxe5dZ.js} +1 -1
  40. data/public/railwatch/assets/{data-table-CVKR2wkq.js → data-table-CzKTEE-O.js} +1 -1
  41. data/public/railwatch/assets/{edit-BwSEVrTG.js → edit-B1kmWkzd.js} +1 -1
  42. data/public/railwatch/assets/{edit-BXF5oNYA.js → edit-D9cx4pbG.js} +1 -1
  43. data/public/railwatch/assets/{edit-D5BRDwLT.js → edit-yv6j9p-V.js} +1 -1
  44. data/public/railwatch/assets/{empty-state-Bn48Gplm.js → empty-state-CW4wclK_.js} +1 -1
  45. data/public/railwatch/assets/{env-layout-CKLL8Ds7.js → env-layout-Kz7wks1x.js} +1 -1
  46. data/public/railwatch/assets/{execution-path-DWovhgLi.js → execution-path-FAuIzOXB.js} +1 -1
  47. data/public/railwatch/assets/{filter-bar-xcbJC93z.js → filter-bar-CJDjWFib.js} +1 -1
  48. data/public/railwatch/assets/{flamegraph-BNbNIuKT.js → flamegraph-EaGkP2NT.js} +1 -1
  49. data/public/railwatch/assets/{frames-Bk39xfJG.js → frames-zZuIaNH9.js} +1 -1
  50. data/public/railwatch/assets/{google-sign-in-button-B5as2ps1.js → google-sign-in-button-2_zgbgVy.js} +1 -1
  51. data/public/railwatch/assets/{index-XjXcV-EO.js → index-B49SWz7K.js} +1 -1
  52. data/public/railwatch/assets/{index-B0xgzHuN.js → index-BHlY4wKe.js} +1 -1
  53. data/public/railwatch/assets/{index-CiaeS-n7.js → index-BZpPtyFY.js} +1 -1
  54. data/public/railwatch/assets/{index-BfitxVBv.js → index-BeVK6jCL.js} +1 -1
  55. data/public/railwatch/assets/{index-C-EwH4lO.js → index-BfbSo01U.js} +1 -1
  56. data/public/railwatch/assets/{index-CrA7jBTK.js → index-BfgncAv6.js} +1 -1
  57. data/public/railwatch/assets/{index-D0mKj3Hg.js → index-BhNszK1k.js} +1 -1
  58. data/public/railwatch/assets/{index-CDXlITyG.js → index-Bu01uWvw.js} +1 -1
  59. data/public/railwatch/assets/{index-w_skgMmK.js → index-C1s_hK3p.js} +1 -1
  60. data/public/railwatch/assets/{index-Zkt4W_CM.js → index-C8Cggnbw.js} +1 -1
  61. data/public/railwatch/assets/{index-BDVShWMs.js → index-CBip6V4z.js} +1 -1
  62. data/public/railwatch/assets/{index-DeLtMWou.js → index-CMJGss5R.js} +1 -1
  63. data/public/railwatch/assets/{index-LKgpCzoR.js → index-CRo3yK20.js} +1 -1
  64. data/public/railwatch/assets/{index-gCvuyG1l.js → index-CWB_p2J8.js} +1 -1
  65. data/public/railwatch/assets/{index-C8JIdb3_.js → index-CWHpndGc.js} +1 -1
  66. data/public/railwatch/assets/{index-CBFCtJAR.js → index-Ca_S4Sc3.js} +1 -1
  67. data/public/railwatch/assets/{index-o1xqGvLo.js → index-CanPDDOa.js} +1 -1
  68. data/public/railwatch/assets/{index-Bd4YpOIO.js → index-Cie90Yat.js} +1 -1
  69. data/public/railwatch/assets/{index-C_hQmVTP.js → index-CiepQ_pR.js} +1 -1
  70. data/public/railwatch/assets/{index-BRvRkfob.js → index-Cm1uCIGN.js} +1 -1
  71. data/public/railwatch/assets/{index-dpG-19ZN.js → index-CzitnnSC.js} +1 -1
  72. data/public/railwatch/assets/{index-Vj93BTR5.js → index-DEUfClv3.js} +1 -1
  73. data/public/railwatch/assets/index-DJKwo-mI.js +1 -0
  74. data/public/railwatch/assets/{index-cTzBycCg.js → index-DK6y0YHp.js} +1 -1
  75. data/public/railwatch/assets/{index-B-FXAo1w.js → index-DMNPpLH9.js} +1 -1
  76. data/public/railwatch/assets/{index-BeQw69GM.js → index-DZO1mSfX.js} +1 -1
  77. data/public/railwatch/assets/{index-BVWapDeB.js → index-Db5wj3M5.js} +1 -1
  78. data/public/railwatch/assets/{index-DV5MdmpY.js → index-Dcy5WktB.js} +1 -1
  79. data/public/railwatch/assets/{index-aPCZX-pW.js → index-DvG0-7Lx.js} +1 -1
  80. data/public/railwatch/assets/{index-Atp1FTgX.js → index-Dvj1wuka.js} +1 -1
  81. data/public/railwatch/assets/{index-COd9lT84.js → index-KyZX46qX.js} +1 -1
  82. data/public/railwatch/assets/{index-S0NVaLj9.js → index-Ze-KP-sl.js} +1 -1
  83. data/public/railwatch/assets/{index-DaE-1xhx.js → index-mhRbWLBM.js} +1 -1
  84. data/public/railwatch/assets/{index-DdVg9LKX.js → index-x099JL5f.js} +1 -1
  85. data/public/railwatch/assets/{index-CcennT28.js → index-x28zb_nf.js} +1 -1
  86. data/public/railwatch/assets/{inertia-CqnzqPVD.js → inertia-Cuyz2ZHO.js} +2 -2
  87. data/public/railwatch/assets/{input-error-ByT28UdP.js → input-error-hog6gGxg.js} +1 -1
  88. data/public/railwatch/assets/{json-viewer-Bnu9RsJL.js → json-viewer-DH2W9HXf.js} +1 -1
  89. data/public/railwatch/assets/{klass-nLiBV-Az.js → klass-DHbDelLk.js} +1 -1
  90. data/public/railwatch/assets/{label-1nSzpSB6.js → label-DdCBgiUn.js} +1 -1
  91. data/public/railwatch/assets/{layout-D7lE1Doo.js → layout-Cueyl7c5.js} +1 -1
  92. data/public/railwatch/assets/{live-dot-UdzOfzJb.js → live-dot-BZgYTYdt.js} +1 -1
  93. data/public/railwatch/assets/{nav-BRCgp2w3.js → nav-BSSGObDZ.js} +1 -1
  94. data/public/railwatch/assets/{new-COyDKOOb.js → new-B8FSb8Bl.js} +1 -1
  95. data/public/railwatch/assets/{new-Cd9zPNJ8.js → new-C39_v2Ll.js} +1 -1
  96. data/public/railwatch/assets/{new-vjRzERL3.js → new-DVOPwaC5.js} +1 -1
  97. data/public/railwatch/assets/{new-C60G72DR.js → new-Dd40nvJR.js} +1 -1
  98. data/public/railwatch/assets/{new-DMJGKjmH.js → new-SxnYe1SE.js} +1 -1
  99. data/public/railwatch/assets/{new-Omvf-c9s.js → new-eP5vKD3Y.js} +1 -1
  100. data/public/railwatch/assets/{onboarding-gUjJQc-6.js → onboarding-CUpZl5KB.js} +1 -1
  101. data/public/railwatch/assets/{origin-identity-M1YUPYS0.js → origin-identity-BYt2uiuo.js} +1 -1
  102. data/public/railwatch/assets/{percentile-picker-BxIVblBw.js → percentile-picker-CeQgllxD.js} +1 -1
  103. data/public/railwatch/assets/{relative-time-DQ4kjowZ.js → relative-time-D5UbF4oO.js} +1 -1
  104. data/public/railwatch/assets/{release-health-Cb1tMOOO.js → release-health-uP-GF3_V.js} +1 -1
  105. data/public/railwatch/assets/{route-BJkoEm7r.js → route-DQAY8JEr.js} +1 -1
  106. data/public/railwatch/assets/{segmented-C4oWE73o.js → segmented-BeIe4uqk.js} +1 -1
  107. data/public/railwatch/assets/{select-ocdQJdQo.js → select-2R16457Z.js} +1 -1
  108. data/public/railwatch/assets/{separator-Ct2UcZ2x.js → separator-D5I0UCB5.js} +1 -1
  109. data/public/railwatch/assets/series-chart-gnrzhmM6.js +1 -0
  110. data/public/railwatch/assets/show-2BkeNRUC.js +2 -0
  111. data/public/railwatch/assets/{show-BLJOyLb_.js → show-B0X1hRQH.js} +1 -1
  112. data/public/railwatch/assets/{show-C8TkpEgm.js → show-BKUV5l5q.js} +1 -1
  113. data/public/railwatch/assets/{show-BAjfddEn.js → show-BQJD_CsF.js} +1 -1
  114. data/public/railwatch/assets/{show-C6OiJ6Hr.js → show-BhmD6Sxp.js} +1 -1
  115. data/public/railwatch/assets/{show-CEhPVMMT.js → show-C3KcnVo2.js} +1 -1
  116. data/public/railwatch/assets/{show-CX_pab9S.js → show-C95cHd06.js} +1 -1
  117. data/public/railwatch/assets/{show-Bsu1rvnK.js → show-CdB4uVQS.js} +1 -1
  118. data/public/railwatch/assets/{show-yEOrk6Pt.js → show-CoCqcVIp.js} +1 -1
  119. data/public/railwatch/assets/{show-DDVsv-uS.js → show-D2LqjEsU.js} +1 -1
  120. data/public/railwatch/assets/{show-D6lgzL6n.js → show-DNpnKyTh.js} +1 -1
  121. data/public/railwatch/assets/{show-eKazflRI.js → show-DOlTxbig.js} +1 -1
  122. data/public/railwatch/assets/{show-Dwiq-8hA.js → show-DQCkttL8.js} +1 -1
  123. data/public/railwatch/assets/{show-CBFBiBY2.js → show-DcxesipC.js} +1 -1
  124. data/public/railwatch/assets/{show-BoVyMQS6.js → show-IGJoc_X0.js} +1 -1
  125. data/public/railwatch/assets/{show-gtwKsTsl.js → show-YsNpqbcI.js} +1 -1
  126. data/public/railwatch/assets/{show-B4bwI9x2.js → show-qNV6H8SH.js} +1 -1
  127. data/public/railwatch/assets/{sort-header-CPM6fhXv.js → sort-header-mEJUM3Av.js} +1 -1
  128. data/public/railwatch/assets/{sparkline-cell-CXCxXPWt.js → sparkline-cell-Dwg7awwh.js} +1 -1
  129. data/public/railwatch/assets/{stat-BNEfE8X1.js → stat-ZtxGU8lE.js} +1 -1
  130. data/public/railwatch/assets/{status-badge-D0aylrM5.js → status-badge-CH5P-Xkj.js} +1 -1
  131. data/public/railwatch/assets/{tenant-path-C4aM_cO0.js → tenant-path-CQoP-BeF.js} +1 -1
  132. data/public/railwatch/assets/{text-link-CP4lqUi6.js → text-link-DHJ8BXx5.js} +1 -1
  133. data/public/railwatch/assets/{textarea-DzJo2ds4.js → textarea-BeQtQyl5.js} +1 -1
  134. data/public/railwatch/assets/{timeline-BNcJ9PMc.js → timeline-CaKQVu48.js} +1 -1
  135. data/public/railwatch/assets/{transition-BX2M1iaq.js → transition-ksDpqhKJ.js} +1 -1
  136. data/public/railwatch/assets/{use-clipboard-C1ApSwzA.js → use-clipboard-DNefo-ky.js} +1 -1
  137. data/public/railwatch/assets/{use-live-DWwLg1Nj.js → use-live-DMTuhKfB.js} +1 -1
  138. data/public/railwatch/manifest.json +1286 -1286
  139. metadata +116 -116
  140. data/public/railwatch/assets/application-B7h1MIhi.css +0 -1
  141. data/public/railwatch/assets/index-DiQ4mqT_.js +0 -1
  142. data/public/railwatch/assets/series-chart-D6upsrSn.js +0 -1
  143. data/public/railwatch/assets/show-CV-lG2b6.js +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6da9b1e629335b1623cfc24e8b18c5a5d17bed076630768b9d947ff2d0b1a5c2
4
- data.tar.gz: 827e8cc13fa4e1373bd6656c851e7fabbdfe2af60c37432c9feeb10801972367
3
+ metadata.gz: 293f64188856fdbadb54fb91bc640441a0996ba3d914d320b778c72536cbb3a8
4
+ data.tar.gz: b10e36a4cbb054e5170d1984003be84d2ea62acee2c1a3bda1c3b560a5f54bab
5
5
  SHA512:
6
- metadata.gz: 741964c31992572e63aeabb9a601482d4a4facb6f895ac1c3fe362c9e0f493f7e7b7636b070bc0909b764e38514495f838e8dbda9de5fef8423cd978fc833281
7
- data.tar.gz: 2eb78e494a27b97451023876f6e72e1cb73bf4aebacb002af148d674841a3f62348c2992cd131d16f7cc75a6308203d68da2738b1431791c4a15fd134b7d3fc4
6
+ metadata.gz: 7e45fc461237f0d9aceccbd99f359b8a81a404d6620600361d3d4fd973f986baf32779ea3921f581a81527c4764687aa8771897c466498860cd8f30c53cd5e4b
7
+ data.tar.gz: 5152d7d1b250d98299e2f675a951ec6f544e0a91405365877b434e8fa6fb1cbfddf061f84fe06aca00cb9202de0092f0016c024779707e69f9d400d33117a007
data/AGENTS.md CHANGED
@@ -4,11 +4,12 @@ About *using* the `railwatch` gem from a Rails application; copy
4
4
  this into that application's repository. Index of everything else:
5
5
  [`llms.txt`](llms.txt).
6
6
 
7
- Railwatch instruments a Rails app end to end and ships linked telemetry to
7
+ Railwatch instruments a Rails app end to end and stores linked telemetry in
8
+ two SQLite databases of the app's own (embedded, the default) or sends it to
8
9
  Railwatch Cloud. Every request, job attempt, scheduled task run, and command is
9
- an **execution**; every query, cache read, log line, outgoing HTTP call, view
10
- render, exception, and span is a child of one, linked by
11
- `execution_id`/`trace_id`. It never writes to the app's database.
10
+ an **execution**; every query, cache read, log line, outgoing HTTP call, LLM
11
+ call, view render, exception, and span is a child of one, linked by
12
+ `execution_id`/`trace_id`. It never writes to the app's primary database.
12
13
 
13
14
  ## Install
14
15
 
@@ -19,24 +20,35 @@ bin/rails generate railwatch:install --prompt-token --kamal-secrets # or: Railw
19
20
  ```
20
21
 
21
22
  With no flags the install is embedded (see [Embedded mode](docs/embedded.md)):
22
- telemetry stays in two SQLite databases the app owns and the dashboard is served
23
- at `/railwatch`. `--cloud`, or any of `--prompt-token`, `--token-stdin`, `--url`,
24
- `--kamal-secrets`, sends it to Railwatch Cloud instead. For the cloud install the generator writes `config/initializers/railwatch.rb`, mounts `Railwatch::Engine`
25
- at `/railwatch`, adds the Kamal `post-deploy` hook and the Inertia browser client
26
- where the app has them, requires `railwatch/rspec` (or `railwatch/minitest`) in the
27
- test helper, and then runs `railwatch:doctor`. A prompted/stdin/environment token
28
- goes into `.env` only when Git confirms that file is ignored; token values are
29
- never printed. Configuration lives only in that
30
- initializer; every option also has a `RAILWATCH_*` environment variable.
23
+ telemetry stays in two SQLite databases the app owns (`railwatch`,
24
+ `railwatch_telemetry`, added to `config/database.yml`), Puma forks one writer
25
+ for them (`plugin :railwatch` in `config/puma.rb`), and the dashboard is served
26
+ at `/railwatch`. It is open in development and answers 401 elsewhere until
27
+ `RAILS_ENV=production bin/rails railwatch:authentication:configure` sets a
28
+ password. An exported `RAILWATCH_TOKEN` alone does not change the mode.
29
+ `--cloud`, or any of `--prompt-token`, `--token-stdin`, `--url`,
30
+ `--kamal-secrets`, sends telemetry to Railwatch Cloud instead.
31
+
32
+ Both modes write `config/initializers/railwatch.rb`, mount `Railwatch::Engine`
33
+ at `/railwatch`, add the Kamal `post-deploy` hook and the Inertia browser client
34
+ where the app has them, and require `railwatch/rspec` (or `railwatch/minitest`)
35
+ in the test helper; the cloud install then runs `railwatch:doctor`. A
36
+ prompted/stdin/environment token goes into `.env` only when Git confirms that
37
+ file is ignored; token values are never printed. Configuration lives only in
38
+ that initializer; every option also has a `RAILWATCH_*` environment variable.
39
+ An embedded install mirrors to Railwatch Cloud as well with a token and
40
+ `c.export_enabled = true`.
31
41
 
32
42
  ## Rake tasks
33
43
 
34
44
  | Task | Does |
35
45
  |---|---|
36
- | `bin/rails railwatch:doctor` | ✓/✗ per check: token, ingest URL, reachability, middleware, engine mount, deploy marker, sample rates, ignored types, Kamal hook, browser client and whether an entrypoint calls it, profiler backend, test matchers. Exits non-zero if the token is missing or the host is unreachable. **Run this first when telemetry is missing.** |
37
- | `bin/rails railwatch:token` | Where to create an ingest token for this app's platform. |
38
- | `bin/rails railwatch:mcp` | Paste-ready MCP client configuration for this app's platform. |
39
- | `bin/rails railwatch:deploy[ref,name,url]` | Records a deploy marker. Use as a release step when not deploying with Kamal. |
46
+ | `bin/rails railwatch:doctor` | ✓/✗ per check. Embedded: databases, migrations, writer process, last write, maintenance, dashboard access, export. Cloud: token, token storage, ingest URL, reachability. Both: middleware, engine mount, deploy, sample rates, ignored types, Kamal hook, browser client and whether an entrypoint calls it, profiler backend, test matchers. Exits non-zero on a missing database, pending migrations, broken export, a missing or tracked token, or an unreachable host. **Run this first when telemetry is missing.** |
47
+ | `bin/rails railwatch:authentication:configure` | Sets the embedded dashboard's HTTP Basic credentials for the current `RAILS_ENV`. |
48
+ | `bin/rails railwatch:export:status` | What the export queue holds and whether it can send. |
49
+ | `bin/rails railwatch:token` | Where to create a Railwatch Cloud ingest token for this app. |
50
+ | `bin/rails railwatch:mcp` | Paste-ready MCP client configuration for this app's Railwatch Cloud. |
51
+ | `bin/rails railwatch:deploy[ref,name,url]` | Records a deploy marker (in the embedded database, or on the cloud). Use as a release step when not deploying with Kamal. |
40
52
  | `bin/rails 'railwatch:sourcemaps[public,true]'` | Uploads Vite source maps for the configured deploy, then deletes acknowledged files. Run after building and before publishing assets. Omit `true` to retain files. See [Source maps](docs/source-maps.md). |
41
53
 
42
54
  ## Facade
@@ -77,9 +89,11 @@ Per action, in a controller class body: `railwatch_sample 0.01, only: :index`,
77
89
  ## Specs
78
90
 
79
91
  `require "railwatch/rspec"` in `spec/rails_helper.rb` (or `"railwatch/minitest"` in
80
- `test/test_helper.rb`). Railwatch must be enabled in the test env — set any
81
- non-blank `RAILWATCH_TOKEN`; records go to an in-memory transport, never over the
82
- wire. All matchers are block matchers.
92
+ `test/test_helper.rb`). Railwatch must be enabled in the test env: an embedded
93
+ install is, and needs `RAILS_ENV=test bin/rails db:prepare` once so its test
94
+ databases exist; a cloud install needs any non-blank `RAILWATCH_TOKEN`. Records
95
+ go to an in-memory transport, never over the wire. All matchers are block
96
+ matchers.
83
97
 
84
98
  ```ruby
85
99
  expect { get "/widgets" }.to have_railwatch_queries(at_most: 6) # or exactly:/at_least:
@@ -102,8 +116,8 @@ melt in production.
102
116
 
103
117
  ## MCP
104
118
 
105
- The platform is an MCP server at `<ingest host>/mcp`. Generate a personal
106
- token at Settings → Profile → "API & MCP token", then:
119
+ Railwatch Cloud (not embedded mode) is an MCP server at `<ingest host>/mcp`.
120
+ Generate a personal token at Settings → Profile → "API & MCP token", then:
107
121
 
108
122
  ```sh
109
123
  claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
@@ -112,10 +126,12 @@ claude mcp add railwatch --transport http https://railwatch.rebulk.com/mcp \
112
126
 
113
127
  `bin/rails railwatch:mcp` prints this and the Claude Desktop, Cursor, VS Code, and
114
128
  Zed equivalents for whichever platform the app points at. Call
115
- `list_applications` first; then `list_issues`, `get_issue`, `get_route`,
116
- `search_requests`, `get_execution`, `explain_query`, `get_profile`,
117
- `search_logs`, `release_health`, `recent_deploys`, `list_alerts`, and the
118
- `triage_issue` / `slow_route` / `daily_summary` prompts. All durations are
129
+ `list_applications` first; then `list_issues`, `get_issue` (stack frames,
130
+ cause, locals, breadcrumbs), `get_route`, `search_requests`, `get_execution`,
131
+ `explain_query`, `get_profile`, `search_logs`, `search_telemetry`,
132
+ `release_health`, `recent_deploys`, `list_alerts`, and the `triage_issue` /
133
+ `slow_route` / `daily_summary` prompts. `update_issue` and `add_comment` write,
134
+ attributed to the token's user and your agent name. All durations are
119
135
  milliseconds; these docs are served at `railwatch://docs/<name>`.
120
136
 
121
137
  ## Gotchas
data/CHANGELOG.md CHANGED
@@ -6,6 +6,34 @@
6
6
  filled in by the release commit, which is also the only commit that
7
7
  touches lib/railwatch/version.rb and Gemfile.lock. See CONTRIBUTING.md. -->
8
8
 
9
+ ## 0.6.1 (2026-09-23)
10
+
11
+ - The docs describe embedded as the default install throughout: two
12
+ commands, then optional Railwatch Cloud. They correct what 0.6.0 left
13
+ stale: an embedded install needs no token; the PostgreSQL and MySQL
14
+ steps; export needs a token first; a `dashboard_user` resolver names
15
+ the operator but does not gate pages; the Puma plugin line must be
16
+ unconditional; an embedded app's test databases need one
17
+ `RAILS_ENV=test bin/rails db:prepare`. MCP is marked as a Railwatch
18
+ Cloud feature. LLM calls are listed.
19
+ - The installer's embedded next steps say to get a token before turning
20
+ on export.
21
+ - Draw the Trend sparklines. Recharts pads every side of a chart by 5px,
22
+ which left the 32x10 table-cell sparkline a plot area 0px tall, so every
23
+ Trend column on every page was blank.
24
+ - LLM calls: a failed call no longer counts as "unpriced". It used no
25
+ tokens and has nothing to price, yet any failure made its model read
26
+ "+N unpriced" beside a complete spend total; the Recent table shows "–"
27
+ for it instead. The model and tool call charts label their bars ok /
28
+ failed instead of HTTP status classes, and the Recent table's Call
29
+ column truncates instead of pushing Detail off the right edge.
30
+ - Issues: the daily chart marks each deploy day with a line and names the
31
+ deploys in the tooltip, instead of printing refs that overprint when
32
+ several ship in a week.
33
+ - Jobs: the recent runs card keeps the job name readable. The exception
34
+ truncates, and the origin user and tenant columns appear from 2xl up
35
+ (the job class page shows them at every width).
36
+
9
37
  ## 0.6.0 (2026-09-22)
10
38
 
11
39
  - Embedded is now the installer's default. `bin/rails generate
data/README.md CHANGED
@@ -9,9 +9,10 @@ microseconds per query, with zero writes to your database.
9
9
  By default all of it stays inside the app:
10
10
  [embedded mode](docs/embedded.md) serves the full dashboard at
11
11
  `/railwatch` out of two SQLite files your app owns, with no token, no
12
- Node, no Redis and no job worker. Railwatch Cloud is optional, for
13
- alerts that arrive when the app is down, MCP for your AI assistant, and
14
- many apps and servers in one place.
12
+ Node, no Redis and no job worker. Railwatch Cloud is optional: it
13
+ delivers alerts to Slack, email, webhooks or Linear (including when the
14
+ app is down), serves MCP to your AI assistant, and puts many apps and
15
+ servers in one place.
15
16
 
16
17
  ## Install
17
18
 
@@ -23,9 +24,12 @@ bin/rails generate railwatch:install # 2. embedded: databases, Puma writer, das
23
24
  Restart and open `/railwatch`. It is open in development; before you
24
25
  deploy, give it a password with
25
26
  `RAILS_ENV=production bin/rails railwatch:authentication:configure`
26
- (it answers 401 in production until you do).
27
+ (it answers 401 in production until you do). On a PostgreSQL or MySQL
28
+ app without the `sqlite3` gem, the generator adds it to the Gemfile;
29
+ run `bundle install` and `bin/rails db:prepare` to finish.
27
30
 
28
- Or send everything to Railwatch Cloud instead:
31
+ To send everything to Railwatch Cloud instead, pass `--cloud` or a token
32
+ option:
29
33
 
30
34
  ```sh
31
35
  bundle add railwatch
@@ -33,10 +37,10 @@ bin/rails generate railwatch:install --prompt-token # hidden token input plus a
33
37
  bin/rails railwatch:doctor # check every piece is wired up after restart
34
38
  ```
35
39
 
36
- Getting the token, the generator's flags, and deploying with Kamal,
40
+ The generator's flags, getting a token, and deploying with Kamal,
37
41
  Docker, Heroku, or Render are covered in
38
- [Getting started](docs/getting-started.md). For a self-hosted deployment
39
- or an unreleased revision, use the Git source instead:
42
+ [Getting started](docs/getting-started.md). For an unreleased revision,
43
+ use the Git source instead:
40
44
 
41
45
  ```ruby
42
46
  gem "railwatch", github: "Rebulk/railwatch"
@@ -52,12 +56,16 @@ gem "railwatch", github: "Rebulk/railwatch"
52
56
  ([Configuration](docs/configuration.md)).
53
57
  - An optional stack profiler through `vernier` or `stackprof`, off by
54
58
  default ([Configuration](docs/configuration.md)).
59
+ - LLM calls made through RubyLLM: tokens, cost, cut-off answers, tool
60
+ calls and workflows, each tied to the request or job that made it
61
+ ([Record types](docs/records.md#llm_call)).
55
62
  - A browser client for Inertia apps: page-visit timing, Core Web Vitals,
56
63
  and browser errors ([Getting started](docs/getting-started.md)).
57
64
  - RSpec and Minitest matchers that turn a query budget into a CI gate
58
65
  ([Testing](docs/testing.md)).
59
- - An MCP server so Claude Code, Cursor, VS Code, or Zed can read your
60
- production data ([AI assistants and MCP](docs/ai-and-mcp.md)).
66
+ - With Railwatch Cloud, an MCP server so Claude Code, Cursor, VS Code, or
67
+ Zed can read your production data
68
+ ([AI assistants and MCP](docs/ai-and-mcp.md)).
61
69
 
62
70
  Configuration lives in `config/initializers/railwatch.rb`; most options
63
71
  also have a `RAILWATCH_*` environment variable.
@@ -79,11 +87,11 @@ expect { get "/widgets" }.not_to have_railwatch_n_plus_one
79
87
 
80
88
  ## Embedded mode
81
89
 
82
- The default install keeps everything inside the application. Telemetry goes to two
83
- SQLite files it owns -- `railwatch` for issues, comments and saved views,
84
- `railwatch_telemetry` for what the app reports -- and the dashboard is
85
- served at `/railwatch` from a bundle shipped inside the gem. Nothing
86
- leaves the machine, and there is nothing else to run.
90
+ Telemetry goes to two SQLite files the app owns -- `railwatch` for
91
+ issues, comments and saved views, `railwatch_telemetry` for what the app
92
+ reports -- and the dashboard is served at `/railwatch` from a bundle
93
+ shipped inside the gem. Nothing leaves the machine unless you turn on
94
+ export to Railwatch Cloud, and there is nothing else to run.
87
95
 
88
96
  Puma forks a single writer process (`plugin :railwatch`, which the
89
97
  installer adds) that owns both files. The web workers hand it batches
@@ -92,20 +100,21 @@ runs the maintenance clock too, so exception grouping, rollups, retention
92
100
  and threshold scans happen without a queue.
93
101
 
94
102
  That dashboard reads every query, log line and exception the app
95
- produced, so outside development it is closed by default the way
96
- Mission Control Jobs is: HTTP Basic is on with no credentials, and every
97
- request is 401 until you set them with
98
- `bin/rails railwatch:authentication:configure`. In development, with no
99
- credentials set, it is open. Apps that
100
- would rather use their own session hand it a `dashboard_user` resolver
101
- instead. [Embedded mode](docs/embedded.md) covers all of it, including
102
- upgrades and what it costs to store.
103
+ produced, so it is gated the way Mission Control Jobs is: HTTP Basic is
104
+ on, and with no credentials every request is 401 until you set them
105
+ with `bin/rails railwatch:authentication:configure`. Development is the
106
+ exception: with no credentials set there, it is open. Apps that would
107
+ rather use their own authentication turn Basic off and gate the pages
108
+ with `base_controller_class` or a routes constraint around the mount; a
109
+ `dashboard_user` resolver then names the operator and authorizes live
110
+ updates, but does not gate pages. [Embedded mode](docs/embedded.md)
111
+ covers all of it, including upgrades and what it costs to store.
103
112
 
104
113
  ## Documentation
105
114
 
106
- - [Getting started](docs/getting-started.md) — five-minute install for a
107
- Rails 8 app, the three optional lines, and deploying with Kamal, Docker,
108
- Heroku, Render, or none of them.
115
+ - [Getting started](docs/getting-started.md) — the embedded install, the
116
+ Railwatch Cloud install and its token, the three optional lines, and
117
+ deploying with Kamal, Docker, Heroku, Render, or none of them.
109
118
  - [Configuration](docs/configuration.md) — every option and `RAILWATCH_*`
110
119
  variable, field by field.
111
120
  - [Record types](docs/records.md) — every record Railwatch ships and every
@@ -113,13 +122,15 @@ upgrades and what it costs to store.
113
122
  - [Testing](docs/testing.md) — the RSpec and Minitest matchers, and a CI
114
123
  performance gate.
115
124
  - [AI assistants and MCP](docs/ai-and-mcp.md) — connecting Claude Code,
116
- Cursor, VS Code, or Zed to your production data.
125
+ Cursor, VS Code, or Zed to your production data through Railwatch
126
+ Cloud.
117
127
  - [Replacing Sentry](docs/replacing-sentry.md) — a step-by-step migration,
118
128
  option by option and call site by call site.
119
129
  - [Coming from Laravel Nightwatch](docs/replacing-nightwatch.md) — the
120
130
  record-type mapping and the sampling model, for Laravel people.
121
- - [Embedded mode](docs/embedded.md) — the whole dashboard inside your
122
- app, telemetry in your own SQLite files, no cloud.
131
+ - [Embedded mode](docs/embedded.md) — the default: the whole dashboard
132
+ inside your app, telemetry in your own SQLite files, and optional
133
+ export to Railwatch Cloud.
123
134
  - [Self-hosting](docs/self-hosting.md) — pointing the gem at your own
124
135
  Railwatch Cloud.
125
136
  - [Troubleshooting](docs/troubleshooting.md) — every failure mode, paired
@@ -118,7 +118,9 @@ module Railwatch
118
118
  cache_read_tokens: group.sum { |r| r.cache_read_tokens.to_i }, cache_write_tokens: group.sum { |r| r.cache_write_tokens.to_i },
119
119
  cost_nanos: group.sum { |r| r.cost_nanos.to_i },
120
120
  priced: group.count { |r| r.cost_nanos },
121
- unpriced: group.count { |r| r.cost_nanos.nil? },
121
+ # A call that failed before the provider answered used no tokens,
122
+ # so there is nothing to price; only an answered call can be unpriced.
123
+ unpriced: group.count { |r| r.cost_nanos.nil? && r.status != "failed" },
122
124
  # A cut-off answer is not an error and will never show in the error
123
125
  # rate, so it needs counting on its own or it stays invisible.
124
126
  truncated: group.count { |r| r.finish_reason == "max_tokens" },
@@ -160,7 +160,7 @@ module Railwatch
160
160
  extra["cache_write_tokens"] += row[:cache_write_tokens].to_i
161
161
  extra["cost_nanos"] += row[:cost_nanos].to_i
162
162
  extra["priced"] += 1 if row[:cost_nanos]
163
- extra["unpriced"] += 1 if row[:cost_nanos].nil?
163
+ extra["unpriced"] += 1 if row[:cost_nanos].nil? && row[:status] != "failed"
164
164
  extra["truncated"] += 1 if row[:finish_reason] == "max_tokens"
165
165
  extra["with_attachments"] += 1 if row[:attachments].to_i.positive?
166
166
  end
data/docs/ai-and-mcp.md CHANGED
@@ -9,6 +9,10 @@ crash-free rates per release. It can also write — resolve an issue, set a
9
9
  priority, leave a comment — and every write is signed with your user and the
10
10
  agent's name in the issue's activity feed.
11
11
 
12
+ MCP is a Railwatch Cloud feature. The embedded dashboard at `/railwatch`
13
+ does not serve it; an embedded install gets it by
14
+ [exporting to the cloud](embedded.md#three-ways-to-run-it).
15
+
12
16
  The endpoint is `<your ingest host>/mcp`. For the hosted platform that's
13
17
  `https://railwatch.rebulk.com/mcp`; if you self-host, it is your own host (see
14
18
  [`self-hosting.md`](self-hosting.md)). The gem knows which one you're on:
@@ -145,9 +149,9 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
145
149
 
146
150
  | Tool | Arguments | Returns |
147
151
  |---|---|---|
148
- | `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns. |
152
+ | `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns (plus `account_slug`/`application_id` when two accounts share a slug). |
149
153
  | `list_issues` | `application_slug?`, `environment?`, `status?` (`open`/`resolved`/`ignored`, default `open`), `limit?` | issues with key, title, kind, status, priority, occurrence and affected-user counts, culprit, first/last seen. |
150
- | `get_issue` | `key` | one issue plus the sample occurrence: exception class, message, stack frames, and the execution it happened inside. |
154
+ | `get_issue` | `key` or `issue_id` | one issue. For an exception, the sample and latest occurrences with stored stack frames, cause, locals, parsed context, browser breadcrumbs, and the execution it happened inside. For a performance or anomaly issue, the metric, limit or baseline, representative records, trend, and deploy comparison. |
151
155
  | `update_issue` | `key`, `status?`, `priority?`, `assignee_email?`, `agent?` | the updated issue. Writes an activity entry attributed to your user and `agent`. |
152
156
  | `add_comment` | `key`, `body`, `agent?` | the created comment, attributed the same way. |
153
157
  | `list_slow_routes` | `application_slug`, `environment`, `window?` | the 20 slowest routes by p95, each with `group_hash`, count, errors, `p95_ms`. |
@@ -157,6 +161,7 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
157
161
  | `explain_query` | `application_slug`, `environment`, `group_hash`, `window?` | the stored query plan for a query group, with the SQL and the sample's duration. `explain` is null unless the app sets `RAILWATCH_CAPTURE_QUERY_EXPLAIN`. `sql` is the normalized shape unless the app also sets `RAILWATCH_CAPTURE_SQL_VALUES`. |
158
162
  | `get_profile` | `application_slug`, `environment`, `profile_id?`, `execution_id?`, `limit?` | the hottest frames of a stack profile — self and total samples, each with a percentage. |
159
163
  | `search_logs` | `application_slug`, `environment`, `q`, `level?`, `limit?` | matching log lines, each with the `execution_id` to expand with `get_execution`. |
164
+ | `search_telemetry` | `application_slug`, `environment`, `resource` (`jobs`/`exceptions`/`logs`/`queries`), `q?`, `window?`, `cursor?`, `limit?` | raw rows of that resource with cursor pagination (`meta.next_cursor`, `meta.has_more`). `q` takes `after:`, `before:`, `user:`, `tenant:`, `deploy:`, plus per-resource keys such as `class:`, `queue:`, `level:`. |
160
165
  | `list_tenants` | `application_slug`, `environment`, `window?`, `q?` | your app's own tenants (whatever it passes to `Railwatch.context(tenant:)`) with request, error, job, exception, and user counts. |
161
166
  | `recent_deploys` | `application_slug`, `environment` | the 20 most recent deploys with ref, name, time, and link. |
162
167
  | `release_health` | `application_slug`, `environment`, `window?` | crash-free session rate, crash-free user rate, and adoption per release. |
@@ -220,7 +225,8 @@ Copy either into your own app's repo to give its agent the same context.
220
225
 
221
226
  ## See also
222
227
 
223
- - [`getting-started.md`](getting-started.md) — install, token, first request.
228
+ - [`getting-started.md`](getting-started.md) — install, the cloud token,
229
+ first request.
224
230
  - [`self-hosting.md`](self-hosting.md) — pointing the gem, and this endpoint,
225
231
  at your own platform.
226
232
  - [`troubleshooting.md`](troubleshooting.md) — when something isn't
@@ -2,18 +2,17 @@
2
2
 
3
3
  Everything below lives on `Railwatch::Configuration`, in
4
4
  `lib/railwatch/configuration.rb`. Set it via
5
- `Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb`.
6
- That file is created by
7
- `bin/rails generate railwatch:install`. Most settings have a `RAILWATCH_*`
8
- env var default; the tables below show which. Explicit values set in the
9
- initializer always win over the env var.
5
+ `Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb`,
6
+ which `bin/rails generate railwatch:install` creates. Most settings have a
7
+ `RAILWATCH_*` env var default; the tables below show which. Explicit
8
+ values set in the initializer always win over the env var.
10
9
 
11
10
  ## Core
12
11
 
13
12
  | Attribute | Env var | Default | Meaning |
14
13
  |---|---|---|---|
15
- | `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn Railwatch on. |
16
- | `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required. |
14
+ | `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. With `transport = :http`, `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn a cloud install on. An embedded install needs no token. |
15
+ | `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required with `transport = :http` and for [export](#export-to-railwatch-cloud). |
17
16
  | `ingest_url` | `RAILWATCH_INGEST_URL` | `https://railwatch.rebulk.com` | Platform base URL. Point at a self-hosted instance to override. |
18
17
  | `allow_http` | `RAILWATCH_ALLOW_HTTP` | `false` | Permit a non-loopback plain HTTP ingest URL. HTTPS is required by default; `localhost`, `127.0.0.1`, and `::1` remain available for local self-hosted development. |
19
18
  | `deploy` | `RAILWATCH_DEPLOY` | auto-detected (order below), then nil | Version tag stamped on every record and used by `railwatch:deploy`. Full 40-character SHAs are shortened to 12 characters. |
@@ -26,7 +25,8 @@ initializer always win over the env var.
26
25
  | `beacon_rate_limit` | `RAILWATCH_BEACON_RATE_LIMIT` | `120` | Beacon POSTs accepted per client IP per minute before `POST /railwatch/beacon` answers 429. The beacon is unauthenticated and keeps every browser error it is sent, so this is what stops a script from spending the app's event quota. Counted in the app's cache store; `0` turns it off. |
27
26
 
28
27
  `Railwatch.enabled?` delegates to `config.enabled?`, which is `@enabled &&
29
- token.present?`. There is no separate "is configured" check elsewhere.
28
+ (local? || token.present?)`. There is no separate "is configured" check
29
+ elsewhere.
30
30
 
31
31
  Deploy detection stops at the first value found: `RAILWATCH_DEPLOY`,
32
32
  `KAMAL_VERSION`, `GIT_REV`, `GIT_SHA`, `SOURCE_VERSION`,
@@ -569,7 +569,7 @@ Rake tasks and Solid Queue jobs are never interactive.
569
569
 
570
570
  | Attribute | Env var | Default | Meaning |
571
571
  |---|---|---|---|
572
- | `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` | Send source snippet lines surrounding each in-application exception frame to Railwatch Cloud. This is on by default for crash context; disable it when source disclosure is outside the application's telemetry policy. |
572
+ | `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` | Record source snippet lines surrounding each in-application exception frame (sent to Railwatch Cloud when the install reports there or exports). This is on by default for crash context; disable it when source disclosure is outside the application's telemetry policy. |
573
573
  | `capture_exception_locals` | `RAILWATCH_CAPTURE_EXCEPTION_LOCALS` | `false` | Snapshot the raising frame's local variables (up to 25, values truncated to 200 chars, run through the same filter as request params) onto each exception, like Sentry's locals panel. Installs a `TracePoint(:raise)`; opt in per environment. |
574
574
  | `capture_request_payload` | `RAILWATCH_CAPTURE_REQUEST_PAYLOAD` | `false` | Capture (redacted) request params — only for a request that raised, never otherwise. |
575
575
  | `capture_job_arguments` | `RAILWATCH_CAPTURE_JOB_ARGUMENTS` | `false` | Add the job's real arguments (`job.serialize["arguments"]`) to each `job_attempt`/`scheduled_task` record, capped at 8 KiB of JSON. Hash arguments run through the same filter as request params. Off by default because job arguments routinely carry PII; `arguments_preview` (argument *shapes* only) is always on regardless. |
@@ -884,7 +884,7 @@ ignore block's building blocks, and are nestable.
884
884
  ## Embedded mode
885
885
 
886
886
  ```ruby
887
- c.transport = :local # RAILWATCH_TRANSPORT; default "http"
887
+ c.transport = :local # RAILWATCH_TRANSPORT; runtime default "http", but the installer writes :local
888
888
  c.issue_prefix = "SHOP" # RAILWATCH_ISSUE_PREFIX; default from the app name
889
889
  c.repository_url = "..." # RAILWATCH_REPOSITORY_URL
890
890
  c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
@@ -892,8 +892,9 @@ c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; on, a
892
892
  c.http_basic_auth_user = "ops" # RAILWATCH_HTTP_BASIC_AUTH_USER, or credentials railwatch.http_basic_auth_user
893
893
  c.http_basic_auth_password = "..." # RAILWATCH_HTTP_BASIC_AUTH_PASSWORD, or credentials railwatch.http_basic_auth_password
894
894
  c.base_controller_class = "AdminController" # RAILWATCH_BASE_CONTROLLER_CLASS; default ActionController::Base
895
- c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; public on purpose
895
+ c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; default false, true makes it public on purpose
896
896
  c.dashboard_user = ->(request) { { id:, name:, email: } or nil }
897
+ c.writer_socket = "tmp/sockets/railwatch-writer.sock" # RAILWATCH_WRITER_SOCKET
897
898
  ```
898
899
 
899
900
  With `transport = :local` the reporter writes each batch into the app's
@@ -905,25 +906,48 @@ railwatch:authentication:configure` has written credentials; a host with
905
906
  its own admin auth turns Basic off and sets `base_controller_class` or a
906
907
  routes constraint. Full walkthrough: [Embedded mode](embedded.md).
907
908
 
909
+ ### Export to Railwatch Cloud
910
+
911
+ An embedded install can also mirror every record to Railwatch Cloud.
912
+ Off unless `export_enabled` is set; a configured token alone sends
913
+ nothing. Only meaningful with `transport = :local`.
914
+
915
+ | Attribute | Env var | Default | Meaning |
916
+ |---|---|---|---|
917
+ | `export_enabled` | `RAILWATCH_EXPORT_ENABLED` | `false` | Mirror every record to the cloud as well as storing it locally. |
918
+ | `export_token` | `RAILWATCH_EXPORT_TOKEN` | `token` | Cloud environment token to send with. |
919
+ | `export_url` | `RAILWATCH_EXPORT_URL` | `{ingest_url}/ingest` | Where to send. |
920
+ | `export_max_bytes` | `RAILWATCH_EXPORT_MAX_BYTES` | `268435456` (256 MiB) | Queue ceiling in bytes. At capacity new work is refused and counted, not swapped for old. |
921
+ | `export_max_deliveries` | `RAILWATCH_EXPORT_MAX_DELIVERIES` | `100000` | Queue ceiling in deliveries. |
922
+ | `export_max_age` | `RAILWATCH_EXPORT_MAX_AGE_SECONDS` | `86400` | How long a queued delivery is kept. At most seven days, past which the receiver no longer recognises it. |
923
+
924
+ `railwatch:doctor` fails when export is enabled and cannot work, and
925
+ `railwatch:export:status` shows the queue. See
926
+ [Embedded mode](embedded.md#three-ways-to-run-it).
927
+
908
928
  ## Rake tasks
909
929
 
910
930
  Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
911
931
  `lib/tasks/railwatch_tasks.rake`:
912
932
 
913
- - **`railwatch:status`** pings `{ingest_url}/ingest/ping` with the
914
- configured token. It aborts if `RAILWATCH_TOKEN` is unset or the ping
915
- fails.
916
- - **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install:
917
- token, ingest URL, `GET /ingest/ping`, `Railwatch::Middleware::Request`
933
+ - **`railwatch:status`** embedded: prints that telemetry is stored in the
934
+ app. Cloud: pings `{ingest_url}/ingest/ping` with the configured token,
935
+ and aborts if `RAILWATCH_TOKEN` is unset or the ping fails.
936
+ - **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install.
937
+ Embedded, it checks both databases and their migrations, telemetry
938
+ disk mode, the maintenance clock, the writer process, dashboard access,
939
+ and export when enabled. Cloud, it checks the token, where the token is
940
+ stored, the ingest URL and its transport security, and
941
+ `GET /ingest/ping`. Then, either way: `Railwatch::Middleware::Request`
918
942
  in the middleware stack, the mounted engine's beacon route,
919
- `config.deploy` and its environment, `REVISION`, Git, or initializer
920
- source, sample rates, ignored record types, the Kamal `post-deploy`
921
- hook, `app/frontend/lib/railwatch.ts`, and whether `railwatch/rspec` or
922
- `railwatch/minitest` is required by the test helper. The last five are
923
- informational. It exits non-zero only when the token is missing or the
924
- ping fails.
925
- - **`railwatch:deploy[ref,name,url]`** POSTs `{deploy, ref, name, url,
926
- server, timestamp, performer, destination, service, commits}` to
943
+ `config.deploy` and its source, sample rates, ignored record types, the
944
+ Kamal `post-deploy` hook, the browser client and whether an entrypoint
945
+ calls it, the profiler backend, and the test matchers. It exits
946
+ non-zero only on the lines marked fatal in
947
+ [Troubleshooting](troubleshooting.md).
948
+ - **`railwatch:deploy[ref,name,url]`** embedded: writes the deploy marker
949
+ to the app's `railwatch` database. Cloud: POSTs `{deploy, ref, name,
950
+ url, server, timestamp, performer, destination, service, commits}` to
927
951
  `{ingest_url}/ingest/deploys`. `deploy` comes from `config.deploy`. It
928
952
  aborts if that's unset. `ref` defaults to `git rev-parse HEAD` when not
929
953
  passed. `performer`/`destination`/`service` come from `KAMAL_PERFORMER`,
@@ -931,13 +955,30 @@ Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
931
955
  `{sha, author, message, at}` objects, newest first, from `git log`. It
932
956
  is empty inside an app container, which has no `.git`. That is why the
933
957
  hook below posts from the deployer instead.
958
+ - **`railwatch:authentication:configure`** writes the embedded
959
+ dashboard's HTTP Basic credentials to the current environment's Rails
960
+ credentials.
961
+ - **`railwatch:export:status`**, **`railwatch:export:rebind`**,
962
+ **`railwatch:export:discard`** show the export queue, clear a
963
+ credential block (abandoning work queued under the old token), and
964
+ abandon everything queued.
965
+ - **`railwatch:vacuum:status`** and **`railwatch:vacuum`** report and
966
+ reclaim the telemetry database's disk
967
+ ([Embedded mode](embedded.md#giving-the-disk-back)).
968
+ - **`railwatch:token`** and **`railwatch:mcp`** print where to create a
969
+ cloud ingest token and paste-ready MCP client configuration.
970
+ - **`railwatch:sourcemaps[directory,delete]`** uploads browser source
971
+ maps ([Source maps](source-maps.md)).
934
972
 
935
973
  ## Kamal integration
936
974
 
937
975
  `bin/rails generate railwatch:install` writes `.kamal/hooks/post-deploy`,
938
- but only if `config/deploy.yml` already exists. It no-ops when
939
- `RAILWATCH_TOKEN` isn't set, and never fails a deploy. Every network call
940
- ends in `|| true`.
976
+ but only if `config/deploy.yml` already exists. With
977
+ `RAILWATCH_TRANSPORT=local` in the deployer's environment it runs
978
+ `bin/rails railwatch:deploy[$KAMAL_VERSION]` in the primary container,
979
+ which records the marker in the embedded database, and stops. Otherwise
980
+ it no-ops when `RAILWATCH_TOKEN` isn't set. It never fails a deploy.
981
+ Every network call ends in `|| true`.
941
982
 
942
983
  The hook runs on the **deployer machine**, not in a container, which is
943
984
  the whole point. That's where the git history lives and where Kamal