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
data/docs/embedded.md CHANGED
@@ -1,18 +1,17 @@
1
1
  # Embedded mode: the dashboard inside your app
2
2
 
3
- This is the default. Railwatch keeps every record in your own application
4
- and serves the full dashboard at `/railwatch`, with no token and no cloud. The gem's
5
- reporter, buffer and sampling are the same; the only difference is
6
- where a batch ends up. In embedded mode it is written straight into a
7
- SQLite database your app owns, and the dashboard reads it back from
8
- there.
3
+ This is what the installer sets up by default. Railwatch keeps every
4
+ record in your own application and serves the full dashboard at
5
+ `/railwatch`, with no token and no cloud. The gem's reporter, buffer and
6
+ sampling are the same as with the cloud; the only difference is where a
7
+ batch ends up. In embedded mode it is written into a SQLite database
8
+ your app owns, and the dashboard reads it back from there.
9
9
 
10
10
  Use it when one server runs the app. Telemetry lands in a file next to
11
11
  your other SQLite databases, so several servers would each see only
12
- their own slice. For more than one server, or for a team that wants one
13
- place for many apps, point the gem at Railwatch Cloud instead
14
- ([Getting started](getting-started.md)); the two are switchable with
15
- one setting.
12
+ their own slice. For more than one server, or one place for many apps,
13
+ add Railwatch Cloud: export alongside embedded, or the cloud on its own
14
+ ([Getting started](getting-started.md#railwatch-cloud-instead)).
16
15
 
17
16
  ## Three ways to run it
18
17
 
@@ -27,23 +26,38 @@ up:
27
26
 
28
27
  Embedded is `c.transport = :local`, which is what the installer writes
29
28
  unless you ask it for the cloud (`--cloud`, or any token or URL option).
30
- Cloud is the gem's default when no initializer says otherwise. "Both" is embedded plus one more line:
29
+ With no initializer setting it, the runtime default is `:http`, the
30
+ cloud. "Both" is embedded plus export, which needs a Railwatch Cloud
31
+ environment token. The default embedded install has none, so set one
32
+ first (`bin/rails railwatch:token` prints where to create it), plus the
33
+ ingest URL if you self-host:
34
+
35
+ ```sh
36
+ RAILWATCH_TOKEN=rw_...
37
+ RAILWATCH_INGEST_URL=https://telemetry.example.com # only when self-hosting
38
+ ```
39
+
40
+ With a token in place, export is one more line:
31
41
 
32
42
  ```ruby
33
43
  c.export_enabled = true # or RAILWATCH_EXPORT_ENABLED=true
34
44
  ```
35
45
 
36
- It reuses the token and ingest URL you already have, so an install that
37
- was pointed at the cloud and moved to embedded needs nothing else to send
38
- to both. Everything captured locally is mirrored — the same records the
39
- same install would have sent had you chosen the cloud — so the hosted
40
- dashboard is as complete as it would be either way.
46
+ It reuses `RAILWATCH_TOKEN` and `RAILWATCH_INGEST_URL`
47
+ (`RAILWATCH_EXPORT_TOKEN` and `RAILWATCH_EXPORT_URL` override them), so
48
+ an install that was pointed at the cloud and moved to embedded needs
49
+ nothing else. Every record captured locally is mirrored, so the hosted
50
+ dashboard is as complete as a cloud-only install's, and `/railwatch`
51
+ keeps working.
41
52
 
42
53
  It is off unless you set that flag. A token being present is not consent:
43
54
  an embedded install that has one configured still sends nothing.
44
55
  `railwatch:doctor` says nothing about export until you ask for it, and
45
- fails loudly if you ask for it and it cannot work. `railwatch:export:status`
46
- shows what is queued.
56
+ fails if you ask for it and it cannot work. `railwatch:export:status`
57
+ shows what is queued. The queue is durable, in the telemetry database,
58
+ and bounded: 256 MiB (`RAILWATCH_EXPORT_MAX_BYTES`), 100,000 deliveries
59
+ (`RAILWATCH_EXPORT_MAX_DELIVERIES`), and a day's age
60
+ (`RAILWATCH_EXPORT_MAX_AGE_SECONDS`, at most seven days).
47
61
 
48
62
  ## Railwatch Cloud runs the same models
49
63
 
@@ -64,10 +78,12 @@ bin/rails generate railwatch:install
64
78
  ```
65
79
 
66
80
  Restart the app and open `/railwatch`; in development it is open with no
67
- password (see [Authentication](#authentication) for production). Then `bin/rails railwatch:doctor`
68
- checks the wiring. The generator creates and migrates both databases
69
- itself; `bin/rails db:prepare`, which a deploy already runs, migrates
70
- them after every gem update.
81
+ password (see [Authentication](#authentication) for production).
82
+ `bin/rails railwatch:doctor` checks the wiring. When the `sqlite3` gem
83
+ is already in the bundle, the generator creates and migrates both
84
+ databases itself; when it is not, see the next section. After that,
85
+ `bin/rails db:prepare`, which a deploy already runs, migrates them after
86
+ every gem update.
71
87
 
72
88
  The engine needs Active Job (its grouping and scan jobs are Active Job
73
89
  classes even though embedded mode calls them directly) and loads it
@@ -81,20 +97,21 @@ databases it adds are SQLite files either way, so the generated entries
81
97
  name `adapter: sqlite3` themselves rather than inheriting your default
82
98
  block, and they need no `&default` anchor to exist.
83
99
 
84
- On a PostgreSQL or MySQL app that means the install is two commands
85
- rather than one, because SQLite's adapter gem will not be in your bundle:
100
+ On a PostgreSQL or MySQL app without the `sqlite3` gem, the install
101
+ takes two more commands:
86
102
 
87
103
  ```sh
88
104
  bin/rails generate railwatch:install # adds gem "sqlite3", writes the config
89
105
  bundle install
90
- bin/rails db:prepare # creates the two SQLite files
106
+ bin/rails db:prepare # creates the two SQLite files
91
107
  ```
92
108
 
93
109
  Verified end to end on both. On a PostgreSQL app and on a MySQL app, the
94
110
  application's own four databases stay where they were, Railwatch's two are
95
111
  files under `storage/`, and neither server gains a single Railwatch table.
96
112
 
97
- What the embedded install writes, on top of what every install writes:
113
+ What the embedded install writes, on top of what every install writes
114
+ (listed in [Getting started](getting-started.md#what-the-generator-writes)):
98
115
 
99
116
  - `config/initializers/railwatch.rb` with `c.transport = :local` and the
100
117
  dashboard's own paths excluded from request capture.
@@ -106,6 +123,8 @@ What the embedded install writes, on top of what every install writes:
106
123
  databases need that form. Each entry's `migrations_paths` points into
107
124
  the gem, so `db:prepare` builds the tables from the gem's own
108
125
  migrations and nothing is copied into `db/`.
126
+ - `plugin :railwatch` at the end of `config/puma.rb`, which forks the
127
+ [writer process](#the-writer-process).
109
128
  - `mount Railwatch::Engine, at: "/railwatch"`, as always.
110
129
 
111
130
  Nothing touches your primary database.
@@ -134,16 +153,19 @@ inside the gem, so the app needs no Node, no Vite and no asset pipeline
134
153
  integration.
135
154
 
136
155
  Not in embedded mode: accounts and members (the operator is whoever your
137
- app lets through), integrations (alerts are recorded, not delivered),
138
- and the MCP server.
156
+ app lets through), integrations (alerts are recorded, not delivered to
157
+ Slack, email, webhooks or Linear), and the
158
+ [MCP server](ai-and-mcp.md). Those are Railwatch Cloud's, and
159
+ [export](#three-ways-to-run-it) gets them without giving up the local
160
+ dashboard.
139
161
 
140
162
  ## Authentication
141
163
 
142
164
  The dashboard shows every query, log line and exception your app
143
165
  produced, so it works the way Mission Control Jobs does: **HTTP Basic
144
- authentication is on and closed by default**. With no credentials
145
- configured every dashboard request is 401, the app logs a warning at
146
- boot, and `railwatch:doctor` says so. Set them with
166
+ authentication is on by default, and closed until credentials exist**.
167
+ With none configured every dashboard request is 401, the app logs a
168
+ warning at boot, and `railwatch:doctor` says so. Set them with
147
169
 
148
170
  ```sh
149
171
  bin/rails railwatch:authentication:configure
@@ -151,11 +173,10 @@ RAILS_ENV=production bin/rails railwatch:authentication:configure
151
173
  ```
152
174
 
153
175
  The one exception is development. There, with Basic on and no
154
- credentials set, the dashboard is open, so a first run is the install and
155
- a page rather than a password step first; Rails already shows full error
156
- pages in development for the same reason. Set credentials there too and
157
- development asks for them like everywhere else. Test, staging and
158
- production are closed until you do.
176
+ credentials set, the dashboard is open, so a first run needs no password
177
+ step; Rails shows full error pages in development for the same reason.
178
+ Set credentials there too and development asks for them like everywhere
179
+ else. Test, staging and production are closed until you do.
159
180
 
160
181
  `railwatch:authentication:configure` writes them to that environment's Rails credentials:
161
182
 
@@ -265,8 +286,10 @@ no user table to check it against. Comments, saved views, issue activity
265
286
  and assignment all key off whatever you return, so a person keeps their
266
287
  own views and their name on their own comments however you identify them.
267
288
 
268
- Returning `nil` refuses the request, which is what makes this an
269
- authorisation rule as well as a label.
289
+ On a page, returning `nil` does not refuse the request: the page renders
290
+ with the default "Operator". With HTTP Basic off, `nil` refuses the
291
+ live-update subscription. Pages are gated by HTTP Basic, your
292
+ `base_controller_class`, or a mount constraint, never by this resolver.
270
293
 
271
294
  ## The writer process
272
295
 
@@ -274,9 +297,14 @@ Puma forks one Railwatch writer from its master when `config/puma.rb`
274
297
  carries the plugin (the embedded install adds it):
275
298
 
276
299
  ```ruby
277
- plugin :railwatch if defined?(Railwatch)
300
+ plugin :railwatch
278
301
  ```
279
302
 
303
+ Leave it unconditional: `bundle exec puma` reads this file before it
304
+ loads the app, so `if defined?(Railwatch)` would be false there and no
305
+ writer would start. The plugin does nothing when Railwatch is off or not
306
+ in embedded mode.
307
+
280
308
  Every web worker keeps its reporter thread, but instead of writing
281
309
  SQLite it hands each batch to the writer over a Unix socket
282
310
  (`tmp/sockets/railwatch-writer.sock`, `RAILWATCH_WRITER_SOCKET`). The
@@ -391,7 +419,7 @@ Railwatch.configure do |c|
391
419
  c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
392
420
  c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; credentials from Rails credentials or env
393
421
  c.base_controller_class = "ActionController::Base" # RAILWATCH_BASE_CONTROLLER_CLASS
394
- c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; "yes, public, on purpose"
422
+ c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; true makes it public on purpose
395
423
  c.dashboard_user = ->(request) { ... }
396
424
  end
397
425
  ```
@@ -403,8 +431,11 @@ a busy app.
403
431
  ## Deploys
404
432
 
405
433
  `bin/rails railwatch:deploy` records the marker in the app's own
406
- database instead of posting it, and the Kamal post-deploy hook does the
407
- same when `RAILWATCH_TRANSPORT=local` is set on the deployer.
434
+ database instead of posting it. The Kamal post-deploy hook runs on the
435
+ deployer, which does not read your initializer, so it only knows the
436
+ install is embedded when `RAILWATCH_TRANSPORT=local` is set in the
437
+ deployer's environment. Then it runs `railwatch:deploy` inside the
438
+ primary container.
408
439
 
409
440
  ## Storage and overhead
410
441
 
@@ -465,6 +496,8 @@ never need to run it again.
465
496
 
466
497
  ## Switching to the cloud later
467
498
 
468
- Set a token and drop `c.transport = :local` (or set
499
+ To keep the local dashboard and add the cloud, turn on
500
+ [export](#three-ways-to-run-it). To move to the cloud entirely, set a
501
+ token and drop `c.transport = :local` (or set
469
502
  `RAILWATCH_TRANSPORT=http`). The local databases can stay; the dashboard
470
503
  at `/railwatch` keeps reading what is there until it is pruned.
data/docs/faq.md CHANGED
@@ -51,20 +51,26 @@ A second gate, `bench/no_db_writes.rb`, drives 200 requests and a job with
51
51
  a `sql.active_record` subscriber watching for any `INSERT`/`UPDATE`/
52
52
  `DELETE` issued from a frame inside `lib/railwatch`, and fails if it finds
53
53
  one. **Railwatch never writes to your application's database.** Records
54
- live in memory and are shipped by a background thread. That is not a
55
- nicety: instrumentation that takes a write lock is what turns a
54
+ live in memory and are delivered by a background thread, to the cloud or
55
+ to the embedded writer process, which owns its own two SQLite files. That
56
+ is not a nicety: instrumentation that takes a write lock is what turns a
56
57
  single-writer SQLite app into a "database is locked" incident.
57
58
 
58
59
  ## Where does the data go, and how long is it kept?
59
60
 
60
- To the platform, over one gzip-NDJSON POST to `{ingest_url}/ingest` per
61
+ Embedded (the default): into the app's own `railwatch_telemetry` SQLite
62
+ database under `storage/`, written by one writer process Puma forks.
63
+ Raw rows are pruned nightly to `retention_days` (7 by default), and
64
+ hourly rollups back the charts. Nothing leaves the machine unless you
65
+ turn on export. See [Embedded mode](embedded.md).
66
+
67
+ Railwatch Cloud: one gzip-NDJSON POST to `{ingest_url}/ingest` per
61
68
  batch. The platform stores each monitored environment's telemetry in its
62
69
  own database, prunes raw rows on a retention window, and keeps hourly
63
- rollups for the charts.
64
-
65
- Retention is set by the account's plan tier, not by the gem — 7, 30, or
66
- 90 days depending on the plan. For a self-hosted install, retention,
67
- backups, and pruning are the platform operator's responsibility.
70
+ rollups for the charts. Retention there is set by the account's plan
71
+ tier, not by the gem — 7, 30, or 90 days depending on the plan. For a
72
+ self-hosted platform, retention, backups, and pruning are the operator's
73
+ responsibility.
68
74
 
69
75
  ## What about PII?
70
76
 
@@ -119,8 +125,9 @@ Yes, on both sides, and it's the first-class target.
119
125
 
120
126
  **In your app:** the gem does no I/O on the request path and never writes
121
127
  to the app database, so there is no contention with SQLite's single
122
- writer. SQL normalization is per adapter, so SQLite, Postgres, MySQL, and
123
- Trilogy all group correctly.
128
+ writer. Embedded mode's own two databases are separate SQLite files,
129
+ whatever the app runs on. SQL normalization is per adapter, so SQLite,
130
+ Postgres, MySQL, and Trilogy all group correctly.
124
131
 
125
132
  **On the platform:** telemetry is stored one SQLite database per
126
133
  monitored environment. That is what makes retention pruning, backup, and
@@ -163,6 +170,11 @@ oldest-job age on `health` records.
163
170
 
164
171
  ## What happens when the platform is unreachable?
165
172
 
173
+ This section is about the cloud transport. In embedded mode the
174
+ equivalent is the writer process being down, covered in
175
+ [Embedded mode](embedded.md#the-writer-process), and export keeps its
176
+ own durable queue.
177
+
166
178
  Nothing, from your app's point of view. This is the property everything
167
179
  else is built around: **delivery never raises into application code.**
168
180
 
@@ -1,26 +1,99 @@
1
1
  # Getting started
2
2
 
3
- Five minutes from `bundle add` to a request on the dashboard, on a
4
- Rails 8 app. Everything below is the gem's own generator and rake tasks;
5
- nothing else has to be wired by hand.
3
+ Two commands on a Rails 8 app, then a dashboard at `/railwatch`.
4
+ Everything below is the gem's own generator and rake tasks; nothing else
5
+ has to be wired by hand.
6
6
 
7
- The installer's default is embedded: the dashboard inside the app at
8
- `/railwatch`, no token and no cloud; see [Embedded mode](embedded.md).
9
- This page is the cloud install, which is the same generator with
10
- `--cloud` or any of the token options below.
11
-
12
- ## 1. Add the gem
7
+ ## Install
13
8
 
14
9
  ```sh
15
10
  bundle add railwatch
11
+ bin/rails generate railwatch:install
16
12
  ```
17
13
 
18
14
  The gem, its Ruby namespace, and its require path share one name:
19
15
  `railwatch`, `Railwatch::*`, `require "railwatch"`.
20
16
 
21
- ## 2. Run the installer
17
+ With no flags the install is [embedded](embedded.md): telemetry stays in
18
+ two SQLite databases the app owns, Puma forks one writer process for
19
+ them (`plugin :railwatch` in `config/puma.rb`), and the dashboard is
20
+ served from the app. No token, no cloud, no job worker. The generator
21
+ creates and migrates both databases before it returns. On a PostgreSQL
22
+ or MySQL app without the `sqlite3` gem it adds the gem instead; run
23
+ `bundle install` and `bin/rails db:prepare` to finish.
24
+
25
+ Restart the app and open `/railwatch`. In development it is open; every
26
+ other environment answers 401 until you give it a password:
27
+
28
+ ```sh
29
+ RAILS_ENV=production bin/rails railwatch:authentication:configure
30
+ ```
31
+
32
+ [Embedded mode](embedded.md#authentication) covers using your own
33
+ admin authentication instead.
34
+
35
+ ### What the generator writes
36
+
37
+ In every mode:
38
+
39
+ - `config/initializers/railwatch.rb`, with every option commented out at
40
+ its default (embedded: `c.transport = :local`).
41
+ - `mount Railwatch::Engine, at: "/railwatch"` in `config/routes.rb`: the
42
+ embedded dashboard and the beacon endpoint the browser client posts to.
43
+ - `.kamal/hooks/post-deploy` — only if `config/deploy.yml` already
44
+ exists.
45
+ - `app/frontend/lib/railwatch.ts` plus the `startRailwatch()` call in your
46
+ Inertia entrypoint — only if `app/frontend/` exists. If it can't find
47
+ an entrypoint it prints the two lines to add.
48
+ - `require "railwatch/rspec"` in `spec/rails_helper.rb`, or
49
+ `require "railwatch/minitest"` in `test/test_helper.rb`.
50
+
51
+ Embedded adds the two databases to `config/database.yml` and the Puma
52
+ plugin line; see [Embedded mode](embedded.md#your-applications-own-database).
53
+
54
+ ### Check the wiring
55
+
56
+ ```sh
57
+ bin/rails railwatch:doctor
58
+ ```
59
+
60
+ With the app running, the embedded checklist starts like this:
61
+
62
+ ```
63
+ ✓ transport: local (telemetry stays in this app; dashboard at the engine mount)
64
+ ✓ railwatch database: storage/development_railwatch.sqlite3
65
+ ✓ railwatch_telemetry database: storage/development_railwatch_telemetry.sqlite3
66
+ ✓ railwatch_telemetry migrations: up to date
67
+ ✓ railwatch migrations: up to date
68
+ ✓ telemetry disk: auto_vacuum=incremental; the nightly prune returns freed pages
69
+ ✓ writer process: listening at /app/tmp/sockets/railwatch-writer.sock
70
+ ✓ last write: 12 seconds ago
71
+ ✓ dashboard access: open in development. Production stays closed until you run `RAILS_ENV=production bin/rails railwatch:authentication:configure`
72
+ ✓ request middleware: Railwatch::Middleware::Request at position 0
73
+ ✓ engine mounted: POST /railwatch/beacon -> railwatch/beacon#create
74
+ ```
75
+
76
+ With the app stopped, `writer process` and `maintenance` are `✗`, and say
77
+ so. Every line and what to do about a `✗` is in
78
+ [`troubleshooting.md`](troubleshooting.md).
79
+
80
+ ### Adding Railwatch Cloud
81
+
82
+ Railwatch Cloud delivers alerts to Slack, email, webhooks or Linear
83
+ (embedded mode only records them), serves the [MCP server](ai-and-mcp.md)
84
+ to your AI assistant, and puts many apps and servers in one place. An
85
+ embedded install can mirror every record to it and keep `/railwatch`:
86
+ set a token ([below](#where-the-token-comes-from)) and
87
+ `c.export_enabled = true`. See
88
+ [Embedded mode](embedded.md#three-ways-to-run-it).
89
+
90
+ ## Railwatch Cloud instead
91
+
92
+ To send telemetry only to Railwatch Cloud, with no local databases, run
93
+ the same generator with `--cloud`:
22
94
 
23
95
  ```sh
96
+ bundle add railwatch
24
97
  bin/rails generate railwatch:install --cloud
25
98
  ```
26
99
 
@@ -51,24 +124,12 @@ bin/rails generate railwatch:install \
51
124
  `config/deploy.yml`, which is the pair of edits Kamal needs to pass a
52
125
  secret through to the containers.
53
126
 
54
- What the generator writes, in every case:
55
-
56
- - `config/initializers/railwatch.rb`, with every option commented out at
57
- its default.
58
- - `mount Railwatch::Engine, at: "/railwatch"` in `config/routes.rb` (the
59
- beacon endpoint the browser client posts to).
60
- - `.kamal/hooks/post-deploy` — only if `config/deploy.yml` already
61
- exists.
62
- - `app/frontend/lib/railwatch.ts` plus the `startRailwatch()` call in your
63
- Inertia entrypoint — only if `app/frontend/` exists. If it can't find
64
- an entrypoint it prints the two lines to add.
65
- - `require "railwatch/rspec"` in `spec/rails_helper.rb`, or
66
- `require "railwatch/minitest"` in `test/test_helper.rb`.
67
-
68
- It finishes by running `railwatch:doctor` for you, so the install either
127
+ It writes the same files as the embedded install
128
+ ([above](#what-the-generator-writes)) minus the databases and the Puma
129
+ plugin, and finishes by running `railwatch:doctor`, so the install either
69
130
  ends in a clean checklist or tells you what is still missing.
70
131
 
71
- ## 3. Where the token comes from
132
+ ### Where the token comes from
72
133
 
73
134
  In Railwatch Cloud, create an application, then an environment inside it
74
135
  (`production`, `staging`, one token each). The token is shown once, on
@@ -80,12 +141,12 @@ update `RAILWATCH_TOKEN`.
80
141
  bin/rails railwatch:token # prints the URL to create/copy a token
81
142
  ```
82
143
 
83
- A token looks like `rw_` followed by 40 characters. The gem is
84
- completely inert without one: `Railwatch.enabled?` is `config.enabled &&
85
- token.present?`, so an app with no token installs no subscribers and
86
- ships nothing.
144
+ A token looks like `rw_` followed by 40 characters. A cloud install is
145
+ inert without one: with `transport = :http`, `Railwatch.enabled?` is
146
+ `config.enabled && token.present?`, so an app with no token installs no
147
+ subscribers and ships nothing.
87
148
 
88
- ## 4. Check the wiring
149
+ ### Check the cloud wiring
89
150
 
90
151
  ```sh
91
152
  bin/rails railwatch:doctor
@@ -93,12 +154,14 @@ bin/rails railwatch:doctor
93
154
 
94
155
  ```
95
156
  ✓ token: rw_9Qv... (43 chars)
157
+ ✓ token storage: no tracked plaintext Railwatch token found
96
158
  ✓ ingest url: https://railwatch.rebulk.com
159
+ ✓ ingest transport security: HTTPS with certificate verification
97
160
  ✓ ingest reachable: GET https://railwatch.rebulk.com/ingest/ping
98
161
  ✓ request middleware: Railwatch::Middleware::Request at position 0
99
162
  ✓ engine mounted: POST /railwatch/beacon -> railwatch/beacon#create
100
163
  ✓ deploy: 8f31c0a42e91 (from GIT_REV)
101
- ✓ sample rates: requests=1.0 jobs=1.0 commands=1.0 scheduled_tasks=1.0 exceptions=1.0
164
+ ✓ sample rates: requests=1.0 jobs=1.0 commands=1.0 scheduled_tasks=1.0 channels=1.0 exceptions=1.0
102
165
  ✓ ignored record types: none
103
166
  ```
104
167
 
@@ -106,13 +169,12 @@ It exits non-zero on three lines: `token` (missing), `token storage`
106
169
  (a plaintext token in a tracked file), and `ingest reachable`. The rest
107
170
  of the checklist is informational. Run it after the token is in place:
108
171
  `ingest reachable` sends the token, so a missing or wrong token makes
109
- the platform answer 401 and that line is `✗` as well. Every line and
110
- what to do about a `✗` is in [`troubleshooting.md`](troubleshooting.md).
172
+ the platform answer 401 and that line is `✗` as well.
111
173
 
112
174
  `bin/rails railwatch:status` is the one-line version: it pings
113
175
  `{ingest_url}/ingest/ping` and prints the ingest URL, deploy, and server.
114
176
 
115
- ## 5. Make one request
177
+ ## Make one request
116
178
 
117
179
  ```sh
118
180
  bin/rails server
@@ -121,9 +183,10 @@ curl http://localhost:3000/
121
183
 
122
184
  Records are batched in-process and flushed every `flush_interval`
123
185
  (2 seconds by default) or every 500 records, whichever comes first, so
124
- the request shows up on the environment's **Requests** page a couple of
125
- seconds after you make it — with its queries, cache reads, view renders,
126
- and log lines already attached to it.
186
+ the request shows up on the **Requests** page (`/railwatch`, or the
187
+ environment in Railwatch Cloud) a couple of seconds after you make it —
188
+ with its queries, cache reads, view renders, and log lines already
189
+ attached to it.
127
190
 
128
191
  ## Three optional lines
129
192
 
@@ -191,8 +254,8 @@ newest issues, deploy markers), Requests (routes table and per-request
191
254
  waterfall of every child record), Jobs, Scheduled tasks, Commands,
192
255
  Exceptions, Queries (slow list and N+1 list with the app line that
193
256
  issued them), Spans, Profiles, Transactions, View renders, Cache, Mail,
194
- Notifications, Broadcasts, Outgoing requests, Storage, Logs,
195
- Deprecations.
257
+ Notifications, Broadcasts, Outgoing requests, LLM (RubyLLM calls with
258
+ tokens, cost, cut-offs and tool calls), Storage, Logs, Deprecations.
196
259
 
197
260
  **Monitoring** — Visits (Inertia page-visit timing and web vitals),
198
261
  Users, Tenants, Deploys, Releases (crash-free session and user rates),
@@ -203,6 +266,15 @@ up, on the account.
203
266
 
204
267
  ## Deploying
205
268
 
269
+ An embedded install deploys like the rest of the app: the two databases
270
+ are files under `storage/`, which the Rails 8 Kamal template already
271
+ mounts as a volume, and the Rails 8 Docker entrypoint's
272
+ `bin/rails db:prepare` migrates them. Give the
273
+ dashboard a password first ([Install](#install)). For deploy markers
274
+ from the Kamal hook, set `RAILWATCH_TRANSPORT=local` in the deployer's
275
+ environment ([Embedded mode](embedded.md#deploys)). The token steps
276
+ below are for a cloud install or export.
277
+
206
278
  ### Kamal
207
279
 
208
280
  Two edits, both of which `--kamal-secrets` makes for you:
@@ -280,9 +352,12 @@ one — the deploy is still recorded.
280
352
  `sentry-rails`.
281
353
  - [`replacing-nightwatch.md`](replacing-nightwatch.md) — for people
282
354
  coming from Laravel.
355
+ - [`embedded.md`](embedded.md) — authentication, the writer process,
356
+ maintenance, disk, and export to the cloud.
283
357
  - [`self-hosting.md`](self-hosting.md) — pointing the gem at your own
284
358
  platform install.
285
359
  - [`troubleshooting.md`](troubleshooting.md) — every `railwatch:doctor`
286
360
  line and what a failure means.
287
361
  - [`faq.md`](faq.md) — overhead, retention, PII, unreachable platform.
288
- - [`ai-and-mcp.md`](ai-and-mcp.md) — asking an AI assistant what broke.
362
+ - [`ai-and-mcp.md`](ai-and-mcp.md) — asking an AI assistant what broke
363
+ (Railwatch Cloud).
data/docs/records.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Record types
2
2
 
3
3
  Every record Railwatch ships is a flat hash. See `lib/railwatch/record.rb`.
4
- This lists all 27, field by field, sourced from the subscriber or patch
4
+ This lists all 28, field by field, sourced from the subscriber or patch
5
5
  that builds each one. Field names below are the hash keys as sent over
6
6
  the wire: symbols in Ruby, strings in the gzip NDJSON payload.
7
7
 
@@ -207,7 +207,7 @@ vs `#execute`. `db:migrate` and other tasks in
207
207
  | `name` | Task name, or `"runner"`. |
208
208
  | `command` | Full invocation, e.g. `"rake db:seed[foo]"` or `"rails runner SomeScript.run"`. |
209
209
  | `exit_code` | 0 on success, `SystemExit`'s status, or 1 on an unhandled exception, clamped to 0-255. |
210
- | `interactive` | `true` on a `bin/rails runner` an engineer typed or piped (`-`, inline code, or a `.rb` file under `config.interactive_runner_paths`); absent otherwise. Such a run ships this record — with its `exit_code` and `exception_preview` — but its exception is not reported. A deployed script (`rails runner script/nightly.rb`), a rake task, and a job are never interactive. See [Console and runner sessions](replacing-sentry.md#console-and-runner-sessions). |
210
+ | `interactive` | `true` on a `bin/rails runner` an engineer typed or piped (`-`, inline code, or a `.rb` file under `config.interactive_runner_paths`); absent otherwise. Such a run ships this record — with its `exit_code` and `exception_preview` — but its exception is not reported. A deployed script (`rails runner script/nightly.rb`), a rake task, and a job are never interactive. See [Console and runner sessions](replacing-sentry.md#12-console-and-runner-sessions). |
211
211
 
212
212
  ### `channel_action`
213
213
 
@@ -526,14 +526,17 @@ adds the `workflow_*` fields. `cost_nanos` is null rather than zero
526
526
  whenever RubyLLM reported no cost or the model registry could not price
527
527
  it — an unpriced call is not a free one.
528
528
 
529
- **Concurrent tool calls are not recorded.** RubyLLM's opt-in
530
- `tool_concurrency` (`:threads` or `:fibers`) runs each tool in a fresh
531
- thread or fiber. `Railwatch::Current` is backed by
532
- `ActiveSupport::IsolatedExecutionState`, which a new thread does not
533
- inherit, so the `tool_call.ruby_llm` event fires with no execution to
534
- attach to and the record is dropped rather than misattributed. This
535
- affects every Railwatch subscriber in an app-spawned thread, not just this
536
- one. Tool concurrency is off by default; with it off, tool calls are
529
+ **Tool calls run on their own threads are not recorded.** RubyLLM's opt-in
530
+ `tool_concurrency: :threads` runs each tool in a fresh thread.
531
+ `Railwatch::Current` is backed by `ActiveSupport::IsolatedExecutionState`,
532
+ which a new thread does not inherit, so the `tool_call.ruby_llm` event
533
+ fires with no execution to attach to and the record is dropped rather
534
+ than misattributed. This affects every Railwatch subscriber in an
535
+ app-spawned thread, not just this one. `:fibers` depends on Rails'
536
+ isolation level: under the default, `:thread`, fibers share their
537
+ thread's state and the tool calls are recorded; with
538
+ `config.active_support.isolation_level = :fiber` they are dropped the same
539
+ way. Tool concurrency is off by default; with it off, tool calls are
537
540
  recorded normally. The model calls themselves are unaffected either way,
538
541
  so cost is always complete.
539
542
 
@@ -2,10 +2,12 @@
2
2
 
3
3
  Railwatch is the same product shape for Rails: one package instruments the
4
4
  framework end to end, records are grouped under the execution that
5
- produced them, and a hosted platform turns them into routes, jobs,
6
- queries, issues, and alerts. If you know Nightwatch, you already know
7
- how to read Railwatch — this page maps the vocabulary and points out the
8
- three places the Rails answer is genuinely different.
5
+ produced them, and a dashboard turns them into routes, jobs, queries,
6
+ issues, and alerts. Unlike Nightwatch, that dashboard runs inside your
7
+ app by default ([embedded mode](embedded.md)); Railwatch Cloud is the
8
+ hosted option. If you know Nightwatch, you already know how to read
9
+ Railwatch — this page maps the vocabulary and points out the three
10
+ places the Rails answer is genuinely different.
9
11
 
10
12
  Railwatch is an independent product and is not affiliated with Laravel or
11
13
  Laravel Nightwatch.
@@ -18,10 +20,10 @@ separate daemon POSTs them.
18
20
 
19
21
  Puma and Solid Queue workers *are* long-lived, so Railwatch skips that
20
22
  tier. A single reporter thread per process holds a bounded buffer
21
- (default 5,000 records, oldest dropped and counted), and gzip-NDJSON
22
- POSTs batches to the platform every 2 seconds or every 500 records. There
23
- is no daemon to install, supervise, or forget to restart, and nothing
24
- between the app and the ingest URL.
23
+ (16 MiB and 10,000 records by default, oldest dropped and counted) and
24
+ flushes batches every 2 seconds or every 500 records: to the embedded
25
+ writer process Puma forks, or as gzip-NDJSON POSTs to Railwatch Cloud.
26
+ There is no daemon to install, supervise, or forget to restart.
25
27
 
26
28
  The thread is re-armed after `fork`, so clustered Puma workers and
27
29
  forked Solid Queue workers each get their own with no `on_worker_boot`
@@ -50,7 +52,7 @@ Nightwatch's types, and what they're called here:
50
52
  | `queued-job` | `enqueued_job` | The enqueue side, in the execution that enqueued it. |
51
53
  | `log` | `log` | Lines at or above `log_level`, plus Rails 8.1 structured `Rails.event` events. |
52
54
  | `user` | `user` | Resolved once per user per process-hour, not once per request. |
53
- | deployment (`nightwatch:deploy`) | `Deploy` on the platform | Posted by `railwatch:deploy` or the Kamal `post-deploy` hook, with up to 50 commits so the platform can diff what shipped. |
55
+ | deployment (`nightwatch:deploy`) | `Deploy` on the dashboard | Recorded by `railwatch:deploy` or the Kamal `post-deploy` hook, with up to 50 commits when Git history is at hand, so the dashboard can diff what shipped. |
54
56
  | request `stages` | parent `stages` | Same idea, Rails boundaries: `middleware_before`, `action`, `render`, `middleware_after`, `body`. Laravel's `bootstrap` has no equivalent in a warm process — Railwatch reports boot time once per process as a `process` record instead. |
55
57
 
56
58
  And the types with no Nightwatch counterpart at all: `storage_op` (Active
@@ -59,7 +61,7 @@ Storage), `view_render`, `span` (your own timed blocks), `attachment`,
59
61
  `session` (release health), `process`, `health` (Puma pool, Active Record
60
62
  pool, Solid Queue backlog), and `profile` (sampled stack profiles).
61
63
 
62
- Field-by-field detail for all 26 is in [`records.md`](records.md).
64
+ Field-by-field detail for all 28 is in [`records.md`](records.md).
63
65
 
64
66
  ## What "execution" means
65
67
 
@@ -76,7 +78,7 @@ it is propagated across services as a W3C `traceparent` on outgoing HTTP
76
78
  — an inbound `traceparent` is adopted, so a trace spans services rather
77
79
  than stopping at the process boundary.
78
80
 
79
- Practically: on the platform you never look at a query in isolation. You
81
+ Practically: on the dashboard you never look at a query in isolation. You
80
82
  open the request, and the query is in its waterfall with everything else
81
83
  that execution did.
82
84
 
@@ -182,7 +184,7 @@ every setting still has an env var — `NIGHTWATCH_*` becomes `RAILWATCH_*`.
182
184
  | `nightwatch:agent` | Nothing — the reporter thread lives in the app process. |
183
185
  | `nightwatch:status` | `bin/rails railwatch:status` |
184
186
  | `nightwatch:deploy {deploy} --ref --name --url` | `bin/rails railwatch:deploy[ref,name,url]`, or the generated `.kamal/hooks/post-deploy` |
185
- | — | `bin/rails railwatch:doctor`, which checks the whole install and exits non-zero if the token or the ingest host is wrong |
187
+ | — | `bin/rails railwatch:doctor`, which checks the whole install and exits non-zero if the embedded databases, the token, or the ingest host is wrong |
186
188
 
187
189
  ## The three things worth knowing about Rails
188
190
 
@@ -211,6 +213,6 @@ See [`testing.md`](testing.md).
211
213
 
212
214
  ## Next
213
215
 
214
- - [`getting-started.md`](getting-started.md) — install, in five minutes.
216
+ - [`getting-started.md`](getting-started.md) — the two-command install.
215
217
  - [`configuration.md`](configuration.md) — every option and env var.
216
- - [`records.md`](records.md) — all 26 record types, field by field.
218
+ - [`records.md`](records.md) — all 28 record types, field by field.