rails_mind 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '087e7d8cc40b60a6ed2ab02feb5ffac9f7a6f1592eac30c964b792482630349f'
4
+ data.tar.gz: 1f073cdb04f317e08a486b14361f12faf84ebfdd0c785c075436896eecbc720c
5
+ SHA512:
6
+ metadata.gz: 95b65914e0d950384712582cb3f4b294e4a04a99ec62c42db53fe2252424932879940997815e7bc3fdaab51689866376cf42a9a3bafb107e2c66e9d335d38adc
7
+ data.tar.gz: 848479498910d94b0f73529eacddb16639f8364585dfef7687d4711229a560e6b1a0e25b5654506d69650ff136acb14f5416883b74a44d694708374c875fdd5b
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-09-25
4
+
5
+ Initial public release of the RailsMind client under the MIT license.
6
+
7
+ - Collect Rails request, error, and ActiveJob telemetry with bounded buffering,
8
+ redaction, retries, and best-effort delivery.
9
+ - Track explicit business events and propagate visitor, user, account, and trace
10
+ context through supported request and job integrations.
11
+ - Integrate with existing Ahoy, Flipper, and OpenTelemetry installations, plus
12
+ an optional Ahoy/Turbo browser adapter.
13
+ - Install with a Rails generator, inspect configuration with `rails_mind:doctor`,
14
+ and check ingestion with `rails_mind:verify`.
15
+ - Default to `https://railsmind.com` and detect deployed Git revisions from
16
+ supported hosting metadata or a `REVISION` file.
17
+
18
+ This is an early release. See [compatibility](docs/compatibility.md) and
19
+ [delivery and privacy limits](docs/sdk.md#bounds-privacy-and-outages) before use.
20
+ The separate hosted RailsMind service remains proprietary.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,81 @@
1
+ # Developing RailsMind
2
+
3
+ This repository owns the MIT-licensed client SDK, browser adapter, tests, and SDK
4
+ documentation. The hosted RailsMind application and its internal integration
5
+ sample are maintained separately and remain proprietary.
6
+
7
+ ## Setup
8
+
9
+ Use Ruby 4.0.6 and Node 22 for the currently verified combination.
10
+
11
+ ```sh
12
+ git clone https://github.com/cmartyn/rails_mind.git
13
+ cd rails_mind
14
+ bundle install
15
+ bundle exec rake test
16
+ node --test javascript/rails_mind.test.js
17
+ bundle exec ruby script/package_smoke.rb
18
+ ```
19
+
20
+ No database or hosted service is required for this repository's tests. See
21
+ [compatibility](docs/compatibility.md) for coverage and limits.
22
+
23
+ ## Testing a development revision
24
+
25
+ Applications normally install a released version from RubyGems.org. To evaluate
26
+ an unreleased change, use a reviewed full commit SHA:
27
+
28
+ ```ruby
29
+ gem "rails_mind", git: "https://github.com/cmartyn/rails_mind.git", ref: "<full commit SHA>"
30
+ ```
31
+
32
+ Commit the application's lockfile and run its integration tests when upgrading.
33
+ A local `path:` dependency can be used temporarily while developing both
34
+ projects; do not commit that override. GitHub credentials are not required to
35
+ read this public repository.
36
+
37
+ ## Packaging and releases
38
+
39
+ `bundle exec ruby script/package_smoke.rb` builds a temporary gem and exercises its
40
+ packaged installer and browser files. `bundle exec rake build` writes the gem to
41
+ `pkg/`. Neither command publishes anything.
42
+
43
+ Publishing uses [RubyGems trusted publishing](https://guides.rubygems.org/trusted-publishing/)
44
+ from `.github/workflows/release.yml`, with no stored RubyGems API key. The gem's
45
+ owner configures these values on RubyGems.org:
46
+
47
+ | Field | Value |
48
+ | --- | --- |
49
+ | Gem name | `rails_mind` |
50
+ | Repository owner | `cmartyn` |
51
+ | Repository name | `rails_mind` |
52
+ | Workflow filename | `release.yml` |
53
+ | GitHub environment | `release` |
54
+
55
+ For the first release, create a pending trusted publisher under the intended
56
+ owner's RubyGems.org profile. A successful first push registers the gem and assigns
57
+ that account ownership. The repository's `release` environment must allow release
58
+ tags; configure required reviewers when additional approval is desired.
59
+
60
+ Before releasing, update `lib/rails_mind/version.rb` and `CHANGELOG.md`, run the
61
+ checks above, complete staging validation appropriate to the documented
62
+ compatibility promise, and merge the release commit into `main`. Keep known
63
+ limitations explicit; a passing unit test suite does not certify a deployed
64
+ customer application.
65
+
66
+ Create an annotated tag matching the gem version and push it:
67
+
68
+ ```sh
69
+ git tag -a v0.1.0 -m "Release rails_mind 0.1.0"
70
+ git push origin v0.1.0
71
+ ```
72
+
73
+ The workflow verifies the tag/version, reruns the Ruby, JavaScript, and package
74
+ checks, then publishes to RubyGems.org. Confirm the published version can be
75
+ installed before announcing the release. Published version numbers cannot be
76
+ reused; fixes need a new version.
77
+
78
+ ## Origin
79
+
80
+ The SDK was extracted into its own repository on 2026-09-15. Its earlier history
81
+ remains in the private application repository; only the client is licensed here.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RailsMind contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # RailsMind SDK
2
+
3
+ The MIT-licensed Ruby client for [RailsMind](https://railsmind.com). One bounded
4
+ collector for Rails requests, errors, jobs, Ahoy events, Flipper observations,
5
+ and selected OpenTelemetry spans. Version 0.1.0 is an early release; see
6
+ [compatibility and validation limits](docs/compatibility.md).
7
+
8
+ ```ruby
9
+ gem "rails_mind", "~> 0.1.0"
10
+ ```
11
+
12
+ Install from RubyGems.org and commit your application's `Gemfile.lock` to pin the
13
+ resolved version. This repository contains the open-source client library. The
14
+ hosted RailsMind service is a separate, proprietary application; its source is
15
+ not included or licensed by this repository. See [development](CONTRIBUTING.md)
16
+ and [release notes](CHANGELOG.md).
17
+
18
+ ```sh
19
+ bundle install
20
+ bin/rails generate rails_mind:install --plan
21
+ bin/rails generate rails_mind:install
22
+ bin/rails rails_mind:doctor
23
+ ```
24
+
25
+ Set **`RAILS_MIND_KEY`** for the destination application environment. It is the only
26
+ required setting; collection stays off without it. The endpoint defaults to
27
+ `https://railsmind.com`. Release IDs are detected from hosting metadata or the
28
+ app's deployed `REVISION` file; telemetry still works when none is available.
29
+ `RAILS_MIND_ENDPOINT` and `RAILS_MIND_RELEASE` are optional overrides. See
30
+ [configuration and release detection](docs/sdk.md#configuration-and-release-detection).
31
+
32
+ After setting the key, run `bin/rails rails_mind:verify` on the app's
33
+ server. Match its check ID in RailsMind **Setup** and wait for **Check processed**.
34
+ Then visit the app to confirm real traffic. This command sends one labeled
35
+ diagnostic check, consumes one event of quota, and leaves business/performance
36
+ metrics alone. It requires a hosted app with installation-check support;
37
+ `rails_mind:doctor` remains read-only. See the
38
+ [verification guide](docs/sdk.md#verify-the-connection) for failure states
39
+ and the limits of this check.
40
+
41
+ ```ruby
42
+ RailsMind.with_context(user_id: current_user.id.to_s, account_id: current_account.id.to_s) do
43
+ RailsMind.track("Invoice paid", invoice_id: invoice.id, amount_cents: invoice.amount_cents)
44
+ end
45
+ ```
46
+
47
+ See [installation and privacy](docs/sdk.md) and [compatibility](docs/compatibility.md).
48
+
49
+ Tests:
50
+
51
+ ```sh
52
+ bundle install
53
+ bundle exec rake test
54
+ node --test javascript/rails_mind.test.js
55
+ bundle exec ruby script/package_smoke.rb
56
+ ruby script_benchmark.rb
57
+ ```
58
+
59
+ Business/request/error events are unsampled, but delivery is best effort. Check `RailsMind.stats` for losses. The SDK does not guarantee durable business-event counts; use a transactional outbox when that is required.
@@ -0,0 +1,66 @@
1
+ # Compatibility and dependency decisions
2
+
3
+ Verified on 2026-09-14. Version numbers come from RubyGems release metadata and the actual installed libraries used in tests. The initial promise is narrow: coexistence with current versions, not importing historical data or replacing existing services.
4
+
5
+ | Foundation | Verified version / license | Current coverage |
6
+ | --- | --- | --- |
7
+ | Ruby / Rails | 4.0.6 / 8.1.3.1; Ruby / MIT | Real Rails sample and notification/error/ActiveJob tests. Gem allows Ruby ≥3.3, Rails ≥7.2 and <9; older combinations are candidates, not yet certified. |
8
+ | Ahoy | [5.5.0, MIT](https://rubygems.org/gems/ahoy_matey) | Actual custom store and mirror tests; default store data/return value preserved; stable IDs; no hosted-only analytics tables. |
9
+ | Flipper | [1.4.2, MIT](https://rubygems.org/gems/flipper) | Actual group targeting and adapter identity preserved; ActiveSupport observation only. No managed flag mode. |
10
+ | OpenTelemetry SDK | [1.13.0, Apache-2.0](https://rubygems.org/gems/opentelemetry-sdk) | Existing exporter/provider and sampler coexistence tested; sampled-out traces do not suppress errors/business events. |
11
+ | OTel Rails components | [0.42.0 aggregate, Apache-2.0](https://rubygems.org/gems/opentelemetry-instrumentation-rails) | Sample selects Rack 0.31.1, ActionPack 0.18.1, ActionView 0.13.0, ActiveJob 0.13.0. Request/SQL/job telemetry through actual libraries. |
12
+ | OTel PG / Net::HTTP | [0.37.0](https://rubygems.org/gems/opentelemetry-instrumentation-pg) / [0.29.0](https://rubygems.org/gems/opentelemetry-instrumentation-net_http), Apache-2.0 | PG statements omitted; exporter allowlist discards raw SQL/HTTP payloads. Net::HTTP auto-instrumentation installed in the sample; external HTTP service interaction not required for the demo. |
13
+ | GoodJob | [4.19.2, MIT](https://rubygems.org/gems/good_job) | Hosted/sample backend; ActiveJob context integration. Sample uses inline execution, so multi-process GoodJob queue/retry behavior needs staging verification. |
14
+ | Turbo | [turbo-rails 2.0.23, MIT](https://rubygems.org/gems/turbo-rails) | Browser adapter contract tests and a real Chromium navigation check cover visits, cached previews, back/forward restoration, Frames, repeat installation, late start, and explicit events. See the release verification below for scope. |
15
+ | Other error reporters | Rails error interface; no mandatory SDK | Subscriber coexistence tested with a second subscriber. Sentry/Honeybadger/Bugsnag/Rollbar detection only; no vendor-specific version certification. |
16
+ | Sidekiq, Solid Queue, Delayed Job | Detection only | ActiveJob jobs inherit the shared integration when used through Rails; backend-specific APIs/direct jobs are not covered. |
17
+ | Bullet / Prosopite | Optional, deferred dependency | Use in development/isolated reproduction. No automatic production scans or forks. |
18
+ | Field Test | Optional, deferred dependency | Detect installed version; no assignment/exposure/import or experiment analysis in this release. |
19
+
20
+ Current Ahoy 5.5 and OTel 1.13 require Ruby ≥3.3. No integration gems are mandatory at SDK runtime beyond Rails and Net::HTTP. Customers select optional dependencies; the installer does not silently add them. MIT/Apache licenses allow this extension strategy without maintaining forks.
21
+
22
+ The preferred foundations remain suitable for the pilot. Ahoy's store model provides a supported buffered-delivery seam but its database associations do not exist in hosted-only mode. Flipper's authoritative configuration remains in the customer's existing adapter; managed flags need an explicit future mode. OTel Ruby tracing supplies the useful mature signal here; broad metrics/logs/exporter parity is deliberately outside this prototype. [Ahoy store contract](https://github.com/ankane/ahoy#data-stores), [Flipper observation contract](https://www.flippercloud.io/docs/instrumentation), [OpenTelemetry Ruby](https://opentelemetry.io/docs/languages/ruby/).
23
+
24
+ Tests are in `test` and browser lifecycle tests in `javascript`. Historical
25
+ end-to-end Rails checks used a separate internal sample application that is not
26
+ part of this public repository. Re-run the matrix when bumping dependencies.
27
+ No external telemetry service, vendor account, historical migration, production
28
+ overhead budget, or published gem installation has been verified by those checks.
29
+
30
+ The local sample-to-hosted HTTP/GoodJob path was exercised with 101 accepted and processed events across all seven signal kinds, zero SDK losses, matched error/request account+trace correlation, and filtered credential probes. This verifies the local wire contract as well as isolated adapters; it does not certify a deployed customer application.
31
+
32
+ ## Run the checks
33
+
34
+ ```sh
35
+ bundle install
36
+ bundle exec rake test
37
+ node --test javascript/rails_mind.test.js
38
+ bundle exec ruby script/package_smoke.rb
39
+ ```
40
+
41
+ CI runs Ruby 4.0.6 / Rails 8.1.3.1 on Linux, plus browser adapter contract tests
42
+ on Node 22. The package smoke check builds and installs a gem in a temporary directory, loads that copy,
43
+ and runs the installer against its packaged templates and browser adapter.
44
+ The sample app remains in the hosted application's repository with its own lockfile.
45
+
46
+ ## 0.1.0 release verification
47
+
48
+ Rechecked on 2026-09-25 with Ruby 4.0.6, the locked Rails 8.1.3.1 dependencies,
49
+ and Node 22:
50
+
51
+ - Ruby suite: 40 tests, 377 assertions, no failures or skips.
52
+ - Browser adapter contract suite: 3 tests passed.
53
+ - Built-gem installation smoke check: 2 tests, 13 assertions; the installed gem
54
+ loaded its own code, generator templates, and browser adapter.
55
+ - A localhost fixture using the actual JavaScript distributed with
56
+ `turbo-rails` 2.0.23 passed in Chromium: initial load, repeated installation,
57
+ completed visits, cached previews, back/forward restoration, labeled and
58
+ unlabeled Frames, form navigation, explicit business outcomes, pageview
59
+ opt-out, stopping listeners, and late initialization. Cached previews and
60
+ Frame loads produced no extra pageviews; form navigation inferred no business
61
+ outcome. Ahoy was a recording stub, so this check validates browser lifecycle
62
+ behavior rather than Ahoy's server endpoint, cookies, or CSRF handling.
63
+
64
+ These checks cover the client package and its documented integrations locally.
65
+ They do not certify other browsers, every allowed Ruby/Rails combination,
66
+ production delivery guarantees, or a deployed customer application's setup.
data/docs/sdk.md ADDED
@@ -0,0 +1,225 @@
1
+ # Customer SDK
2
+
3
+ The gem is `rails_mind`, namespace `RailsMind`. Install the MIT-licensed client
4
+ from RubyGems.org and commit the application lockfile to retain the selected
5
+ version. The hosted RailsMind service is a separate, proprietary application.
6
+ Version 0.1.0 is an early release with the [documented compatibility limits](compatibility.md).
7
+
8
+ ## Install
9
+
10
+ ```ruby
11
+ # Customer Gemfile
12
+ gem "rails_mind", "~> 0.1.0"
13
+ ```
14
+
15
+ ```sh
16
+ bundle install
17
+ bin/rails generate rails_mind:install --plan
18
+ bin/rails generate rails_mind:install
19
+ bin/rails rails_mind:doctor
20
+ ```
21
+
22
+ The plan inspects `Gemfile.lock` and prints selected versions, coexistence decisions, and missing options. Installation adds one initializer, preserves an existing initializer even with `--force`, and does not change migrations, dependencies, Ahoy stores, Flipper adapters, or OpenTelemetry providers. The doctor is read-only and does not contact the server or print credentials.
23
+
24
+ ## Configuration and release detection
25
+
26
+ Set only the environment-scoped ingest key on the customer's server:
27
+
28
+ ```sh
29
+ RAILS_MIND_KEY=<environment-scoped-credential>
30
+ ```
31
+
32
+ The endpoint defaults to `https://railsmind.com`. To use a local or self-hosted
33
+ RailsMind instance, set `RAILS_MIND_ENDPOINT` (for example,
34
+ `http://127.0.0.1:3000`). An unset or whitespace-only override uses the hosted
35
+ default. HTTPS is required except for localhost/loopback development. The key
36
+ controls application/environment scope; never give it to browser JavaScript.
37
+ Collection stays off without a nonblank key. `apm`, `errors`, `analytics`, and
38
+ `flags` can each be disabled in the initializer.
39
+
40
+ Release detection uses the first nonblank value in this order:
41
+
42
+ 1. `RAILS_MIND_RELEASE` — optional explicit override.
43
+ 2. `GIT_REVISION` — existing deployment convention.
44
+ 3. `RENDER_GIT_COMMIT` — [Render's automatic runtime commit](https://render.com/docs/environment-variables).
45
+ 4. `RAILWAY_GIT_COMMIT_SHA` — [Railway deployments triggered by GitHub](https://docs.railway.com/variables/reference).
46
+ 5. `HEROKU_BUILD_COMMIT`, then `HEROKU_SLUG_COMMIT` — [Heroku dyno metadata](https://devcenter.heroku.com/articles/dyno-metadata); metadata features must be enabled, and the slug variable is deprecated.
47
+ 6. `KAMAL_VERSION` — [Kamal's application-container version](https://github.com/basecamp/kamal/blob/master/lib/kamal/commands/app.rb), only when it is a full 40- or 64-character Git commit SHA.
48
+ 7. The `REVISION` file inside `Rails.root` — for [Hatchbox deployments](https://jumpstartrails.com/discussions/appsignal-deploy-tracking) and other deployers that write this file.
49
+
50
+ Values are trimmed. The revision file is read once when configuration is created,
51
+ with a 500-byte limit; absent, blank, oversized, unreadable, or invalidly encoded
52
+ files are ignored. Detection does not run Git or inspect the working directory.
53
+ If no release is available, it remains unset and telemetry is still delivered.
54
+ Provider-specific metadata availability depends on the deployment type; there
55
+ is no universal runtime variable. Build-only and instance-specific IDs are not
56
+ used automatically. `HEROKU_RELEASE_VERSION`, arbitrary Kamal version labels,
57
+ and [Fly.io's `FLY_IMAGE_REF`](https://fly.io/docs/machines/runtime-environment/)
58
+ are also ignored: they can identify deployments or images rather than Git
59
+ revisions, while Assist uses releases to locate the corresponding code. Explicit
60
+ release overrides should identify a commit or ref in the connected repository. For DigitalOcean App Platform, commit metadata is a
61
+ [bindable value](https://docs.digitalocean.com/products/app-platform/how-to/use-environment-variables/),
62
+ not an automatically named runtime variable; optionally bind it to `GIT_REVISION`.
63
+
64
+ Explicit `config.endpoint` and `config.release` assignments still take precedence.
65
+ When upgrading an existing generated initializer, remove the old endpoint/release
66
+ assignments that directly read `ENV` so they do not overwrite the new defaults
67
+ with `nil` or bypass platform detection. The installer preserves existing files.
68
+ The doctor reports whether the effective endpoint, key, and release are present,
69
+ without printing credential values or contacting the server.
70
+
71
+ ## Verify the connection
72
+
73
+ From the customer app's server, using the same environment settings as the running app:
74
+
75
+ ```sh
76
+ bin/rails rails_mind:verify
77
+ ```
78
+
79
+ This sends one labeled installation check through the SDK's redaction, collector,
80
+ HTTP transport, and the normal ingestion queue. It prints a check ID and exits
81
+ successfully only when delivery is confirmed without drops. It has a ten-second
82
+ delivery deadline; a timeout can still mean the server received the check, so look
83
+ for its ID before retrying. Credentials are never printed. `before_send` may
84
+ suppress the check; changing its kind or ID is rejected. The optional signal
85
+ switches do not disable this explicitly requested check.
86
+
87
+ Open **Setup** in RailsMind, select the environment that owns the key, and match
88
+ the ID under **Latest installation check**. **Check processed** confirms the path
89
+ through the background worker. HTTP delivery alone does not confirm processing.
90
+ Then visit a page in your app and confirm **Recent telemetry** under app activity:
91
+ the command runs in its own process and cannot certify the configuration of an
92
+ already-running web or job process.
93
+
94
+ The check uses one event of the daily quota and expires with normal retention.
95
+ Its dedicated `check` signal carries the SDK version without customer identity,
96
+ trace, or release fields. It does not add business events, request measurements,
97
+ errors, or releases. Update the hosted app and run its migrations before using
98
+ this command with an older deployment that does not yet accept `check` signals.
99
+
100
+ Setup refreshes connection health every five seconds while visible, without
101
+ replacing unfinished setup forms. It shows active key use, first/latest retained
102
+ event arrival, processing backlog, and app activity. Arrival times come from the
103
+ server, so an old occurrence timestamp does not make a newly received event look
104
+ undelivered. A pending event older than two minutes needs investigation; app
105
+ traffic absent for fifteen minutes is shown as quiet, which may be normal.
106
+ Installation checks do not reset the app-activity clock. The panel reports the
107
+ environment's retained data, not lifetime history or SDK loss counters.
108
+
109
+ If a check fails, inspect the endpoint, active key, outbound HTTPS, and daily
110
+ quota. A delivered check missing from Setup may belong to a different environment.
111
+ If it remains pending, the RailsMind operator should inspect the telemetry worker
112
+ and failed jobs. Ingest keys remain write-only; viewing the confirmation requires
113
+ a signed-in workspace member.
114
+
115
+ ## Identity and explicit outcomes
116
+
117
+ ```ruby
118
+ # ApplicationController: a before_action inside the SDK request boundary.
119
+ RailsMind.identify(
120
+ visitor_id: ahoy.visitor_token,
121
+ user_id: current_user&.id,
122
+ account_id: current_account&.id
123
+ )
124
+
125
+ # Record an outcome after it commits, or inside an application after_commit.
126
+ RailsMind.track("Invoice paid", invoice_id: invoice.id, amount_cents: invoice.amount_cents)
127
+
128
+ # Explicit reporting also works when the current trace is not retained.
129
+ RailsMind.capture_exception(error, handled: true)
130
+ ```
131
+
132
+ Context is fiber-local and restored around requests/jobs. `identify` replaces the three identity values, so call it with all available values; passing nil clears that value. Preserve Ahoy's visitor ID when a visitor logs in, and emit `$identify` through Ahoy authentication to connect later events. Account IDs represent the current tenant, never a guessed company from email. Logout/account switching must update identity; a new request starts clean. Historical anonymous events are not rewritten or merged across accounts by the SDK.
133
+
134
+ ActiveJob serializes only the context identifier allowlist under `rails_mind_context`, then restores it while executing. Arguments are not inspected. This covers GoodJob through ActiveJob; direct Sidekiq jobs require a future explicit integration. Distributed trace propagation belongs to the existing OpenTelemetry library; choose ActiveJob `propagation_style: :child` for a linked trace tree when configuring a new provider.
135
+
136
+ ## Ahoy
137
+
138
+ Ahoy's documented store extension points preserve existing `ahoy.track` calls. Choose one mode explicitly. [Ahoy custom stores](https://github.com/ankane/ahoy#data-stores).
139
+
140
+ For an established installation, keep the current `Ahoy::Store` definition, storage, ID generator, user method, and cookies, then add this after its configuration:
141
+
142
+ ```ruby
143
+ require "rails_mind/integrations/ahoy"
144
+ RailsMind::Integrations.mirror_ahoy!(Ahoy::Store)
145
+ ```
146
+
147
+ The existing store runs first with its original hash. Its return value and exceptions survive. RailsMind then mirrors visits/events/authentication without modifying the data. Repeated installation does not add another wrapper. Existing event IDs remain stable on transport retries; non-UUID Ahoy IDs map deterministically into UUIDv8 values.
148
+
149
+ For a new hosted-only installation:
150
+
151
+ ```ruby
152
+ require "rails_mind/integrations/ahoy"
153
+ Ahoy.api = true # Only when using the optional browser adapter.
154
+ Ahoy.geocode = false
155
+ class Ahoy::Store < RailsMind::Integrations::AhoyStore
156
+ end
157
+ ```
158
+
159
+ This store requires no growing analytics tables. It does not offer database-backed `Ahoy::Visit` objects, `visitable` associations, geocoding storage, historical import, or cookie-free persisted visit lookup. Applications using those behaviors should keep their current store and mirror. Disable an existing duplicate pageview handler only as a deliberate customer configuration change.
160
+
161
+ ## OpenTelemetry
162
+
163
+ Use selected tracing libraries, not `use_all`. The locally verified setup uses
164
+ Rack, ActionPack, ActionView, ActiveJob, PG, and Net::HTTP, with PG configured as
165
+ `db_statement: :omit`. Avoid additionally instrumenting the same SQL operation
166
+ at both ActiveRecord and PG layers. The Rails aggregate instrumentation package
167
+ loads component libraries; selecting only the Rails package itself does not
168
+ activate the child integrations. Follow the public
169
+ [Ruby instrumentation guide](https://opentelemetry.io/docs/languages/ruby/instrumentation/)
170
+ and [PG instrumentation documentation](https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/pg)
171
+ for your application's setup.
172
+
173
+ An existing provider remains authoritative:
174
+
175
+ ```ruby
176
+ # Run after your existing OpenTelemetry::SDK.configure block.
177
+ require "rails_mind/integrations/open_telemetry"
178
+ RailsMind::Integrations::OpenTelemetry.attach!
179
+ ```
180
+
181
+ Attachment adds one idempotent processor; it does not replace the global provider, sampler, propagator, instrumentation, or other exporters. Its exporter only sanitizes and enqueues locally; the shared worker performs network I/O. Span context is attached at start so identity survives later export. Known safe attributes are allowlisted. Raw span names, exception span events, HTTP bodies/headers, SQL statements, and bind values are not exported to RailsMind. Existing third-party exporters retain their own behavior and privacy settings. [OpenTelemetry exporters](https://opentelemetry.io/docs/languages/ruby/exporters/).
182
+
183
+ Request durations/counts and error reports come independently from Rails notifications/error reporting, even when detailed traces are sampled out. Request measurements cover controller actions, not assets/health checks rejected before ActionController or streaming-body completion. Repeated-query evidence contains only normalized-query hashes and counts; it is a suspicion, not proof of an N+1. Hash normalization handles common literals, not every SQL grammar. Use Bullet/Prosopite or an explicit query-bound test in development to verify fixes. No production object scans are installed.
184
+
185
+ Jobs record duration, failure/retry/discard lifecycle, execution count, queue name, and enqueue-to-start delay where ActiveJob exposes it. Scheduled delay is included in the enqueue-to-start number. Signals do not include arguments. Full metrics/logs SDK integration is deferred; v0.1 computes hosted aggregates from unsampled request events and does not capture application logs.
186
+
187
+ ## Flipper and browser behavior
188
+
189
+ The SDK subscribes to `feature_operation.flipper`. It observes names, operations, boolean results, and duration without changing adapters, groups, actors, rollout logic, or cache. Flipper's existing Rails instrumentation emits these events; applications with a custom instrumenter must deliberately opt into ActiveSupport notifications. Actor IDs are not collected. An evaluation is not experiment exposure. Managed flags and experiments are not implemented in this SDK. [Flipper instrumentation](https://www.flippercloud.io/docs/instrumentation).
190
+
191
+ Run `bin/rails generate rails_mind:install --browser` to copy the optional module. Importmap users pin it, then pass their existing Ahoy instance:
192
+
193
+ ```javascript
194
+ import ahoy from "ahoy"
195
+ import { start } from "rails_mind"
196
+ const analytics = start({ ahoy, frames: true })
197
+ ```
198
+
199
+ Add `<meta name="rails-mind-page" content="Invoices#index">` to supply a stable page label. The adapter transmits no URL or query string. It counts the initial page and each completed Turbo visit once, ignores cached preview renders, and keeps `turbo:frame-load` as a separate `$frame_view` only for Frames with `data-analytics-frame`. It does not infer payment/signup success from forms or HTTP responses. Set `pageviews: false` if the application already counts pageviews. Repeated installation returns the same adapter. Browser delivery uses Ahoy's same-origin endpoint/cookies and existing CSRF policy; there is no browser ingestion credential. [Turbo events](https://turbo.hotwired.dev/reference/events).
200
+
201
+ ## Bounds, privacy, and outages
202
+
203
+ Defaults are one lazy worker per process, 1,000 queued/in-flight events, 16 KiB per event, at most 100 events and 256 KiB per batch, and a two-second flush interval. Overflow drops the newest event. Retries retain the same encoded bytes/UUIDs; only HTTP 429, 5xx, and transient transport failures retry, at most three retries with bounded backoff. Other 4xx responses drop the batch. Redirects are not followed. Shutdown gets two seconds by default, then discards outstanding telemetry; forks start a fresh child queue rather than resending the parent's buffer.
204
+
205
+ ```ruby
206
+ RailsMind.stats
207
+ # => { enqueued:, delivered:, dropped:, retried:, queued:, in_flight:,
208
+ # losses: { "event.overflow" => 3, "error.delivery" => 1 } }
209
+ RailsMind.flush(timeout: 2)
210
+ ```
211
+
212
+ These are per-process counters. The doctor prints them for its process; inspect running app stats through your existing operational tooling. The hosted app does not currently receive loss counters. Request/business/error signals are unsampled, but process crashes, rejected payloads, redaction hooks, exhausted retries, or queue pressure can still undercount. This is best-effort monitoring, not an accounting ledger. A future transactional outbox can provide durable business-event delivery; the current implementation does not claim that guarantee.
213
+
214
+ Redaction occurs before buffering and again after custom `before_send`. Sensitive property names are filtered, nesting/string/collection sizes are capped, emails and common credential forms are scrubbed, exception messages are omitted by default, and backtraces drop machine-home prefixes. Bodies, cookies, credentials, SQL binds, and job arguments are never selected by integrations. Arbitrary user-provided text may contain sensitive content that generic redaction cannot recognize: use small, explicitly reviewed business properties. Opting into exception messages requires reviewing `capture_exception_message` and your custom scrubbing policy.
215
+
216
+ The final local synthetic burst benchmark on Ruby 4.0.6 processed 10,000 calls in 1.7612 seconds (176.12 μs/call), delivering all 10,000 with zero drops/retries at the 1,000-event queue bound. This measures local sanitization/enqueue with a no-op transport, not production request overhead or network delivery. Earlier runs did overflow; thread scheduling and downstream speed affect the result. Deterministic tests separately verify overflow losses and bounded shutdown during a hung transport. Run `ruby script_benchmark.rb` on the deployment hardware before setting a production budget.
217
+
218
+ ## Verified local pipeline
219
+
220
+ On 2026-09-14 the real sample Rails application sent 101 events through this SDK's HTTP transport to the local hosted ingestion API; GoodJob processed all 101. They included 90 spans, 3 requests, 3 business events, 2 Flipper observations, 1 visit, 1 error, and 1 job. There were no collector drops or retries. The intentional error shared its request's trace and account IDs. Every request/business/error/flag/job event had account context; the visit and some early spans preceded identity and therefore did not. The list reported seven repeated queries. Credential-key privacy probes arrived as `[FILTERED]` without their secret values.
221
+
222
+ This historical check used an internal sample application that is not included
223
+ in the public client repository. To verify your own installation, follow
224
+ [Verify the connection](#verify-the-connection). External hosted deployment and
225
+ published-gem installation remain unverified by that historical check.
@@ -0,0 +1,49 @@
1
+ // Uses the application's Ahoy endpoint/cookies. Never put the server token here.
2
+ // Do not run alongside another automatic pageview handler.
3
+ const installations = new WeakMap()
4
+
5
+ export function start({ ahoy, document: doc = document, turbo = true, pageviews = true, frames = false,
6
+ page = () => doc.querySelector('meta[name="rails-mind-page"]')?.content || "page" } = {}) {
7
+ if (!ahoy || typeof ahoy.track !== "function") throw new TypeError("Pass the existing Ahoy instance")
8
+ if (installations.has(doc)) return installations.get(doc)
9
+ const listeners = []
10
+ let visit = 0
11
+ let trackedVisit = -1
12
+
13
+ function on(name, listener) {
14
+ doc.addEventListener(name, listener)
15
+ listeners.push([name, listener])
16
+ }
17
+
18
+ function trackPage() {
19
+ if (!pageviews || trackedVisit === visit || doc.documentElement.hasAttribute("data-turbo-preview")) return
20
+ trackedVisit = visit
21
+ ahoy.track("$pageview", { page: String(page()).slice(0, 160), navigation: turbo ? "turbo" : "document" })
22
+ }
23
+
24
+ if (turbo) {
25
+ on("turbo:visit", () => { visit += 1 })
26
+ on("turbo:load", trackPage)
27
+ } else {
28
+ on("DOMContentLoaded", trackPage)
29
+ }
30
+ // Supports loading the adapter after the initial page load, without duplicating
31
+ // a subsequent turbo:load for that same initial navigation.
32
+ if (doc.readyState === "complete") trackPage()
33
+
34
+ if (frames) on("turbo:frame-load", (event) => {
35
+ const label = event.target?.dataset?.analyticsFrame
36
+ if (label) ahoy.track("$frame_view", { frame: String(label).slice(0, 100), page: String(page()).slice(0, 160) })
37
+ })
38
+
39
+ const api = {
40
+ // The caller records a business outcome only after it actually succeeds.
41
+ track: (name, properties = {}) => ahoy.track(name, properties),
42
+ stop() {
43
+ listeners.forEach(([name, listener]) => doc.removeEventListener(name, listener))
44
+ installations.delete(doc)
45
+ }
46
+ }
47
+ installations.set(doc, api)
48
+ return api
49
+ }
@@ -0,0 +1,37 @@
1
+ require "rails/generators"
2
+ require "rails_mind/diagnostic"
3
+
4
+ module RailsMind
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+ class_option :plan, type: :boolean, default: false, desc: "Print the integration plan without writing files"
9
+ class_option :browser, type: :boolean, default: false, desc: "Copy the optional Ahoy/Turbo adapter without enabling it"
10
+
11
+ def inspect_application
12
+ say JSON.pretty_generate(Diagnostic.new(root: destination_root).report)
13
+ end
14
+
15
+ def add_initializer
16
+ return if options[:plan]
17
+ path = "config/initializers/rails_mind.rb"
18
+ if File.exist?(File.join(destination_root, path))
19
+ say_status :skip, "#{path} already exists", :yellow
20
+ else
21
+ template "initializer.rb.tt", path
22
+ end
23
+ end
24
+
25
+ def add_browser_adapter
26
+ return if options[:plan] || !options[:browser]
27
+ path = "app/javascript/rails_mind.js"
28
+ if File.exist?(File.join(destination_root, path))
29
+ say_status :skip, "#{path} already exists", :yellow
30
+ else
31
+ copy_file File.expand_path("../../../../javascript/rails_mind.js", __dir__), path
32
+ end
33
+ say "Pin rails_mind in importmap.rb and start it with your existing Ahoy instance. See docs/sdk.md."
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ RailsMind.configure do |config|
2
+ config.token = ENV["RAILS_MIND_KEY"] # Environment-scoped server credential. Never expose to browsers.
3
+
4
+ # Endpoint defaults to https://railsmind.com; RAILS_MIND_ENDPOINT overrides it.
5
+ # Release is detected from hosting metadata or Rails.root/REVISION when available.
6
+ # RAILS_MIND_RELEASE is an optional override. Neither variable is required.
7
+
8
+ # Optional capabilities can be switched off independently.
9
+ # config.apm = false
10
+ # config.errors = false
11
+ # config.analytics = false
12
+ # config.flags = false
13
+
14
+ # Exception messages are omitted by default. Review before enabling.
15
+ # config.capture_exception_message = true
16
+ # config.before_send = ->(event) { event } # Return nil to drop an event.
17
+ end
18
+
19
+ # No existing Ahoy store, Flipper adapter, or OpenTelemetry provider is changed.
20
+ # Run `bin/rails rails_mind:doctor` and follow docs/sdk.md for opt-in integrations.