source_monitor 0.1.1 → 0.1.2

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +15 -11
  5. data/config/coverage_baseline.json +350 -107
  6. data/docs/deployment.md +8 -0
  7. data/docs/gh-cli-workflow.md +5 -1
  8. data/docs/setup-validation-log.md +36 -0
  9. data/docs/setup.md +144 -0
  10. data/lib/generators/source_monitor/install/install_generator.rb +1 -1
  11. data/lib/source_monitor/setup/bundle_installer.rb +17 -0
  12. data/lib/source_monitor/setup/cli.rb +51 -0
  13. data/lib/source_monitor/setup/dependency_checker.rb +165 -0
  14. data/lib/source_monitor/setup/detectors.rb +67 -0
  15. data/lib/source_monitor/setup/gemfile_editor.rb +29 -0
  16. data/lib/source_monitor/setup/initializer_patcher.rb +59 -0
  17. data/lib/source_monitor/setup/install_generator.rb +22 -0
  18. data/lib/source_monitor/setup/migration_installer.rb +40 -0
  19. data/lib/source_monitor/setup/node_installer.rb +27 -0
  20. data/lib/source_monitor/setup/prompter.rb +34 -0
  21. data/lib/source_monitor/setup/requirements.rb +51 -0
  22. data/lib/source_monitor/setup/shell_runner.rb +14 -0
  23. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +79 -0
  24. data/lib/source_monitor/setup/verification/printer.rb +24 -0
  25. data/lib/source_monitor/setup/verification/result.rb +68 -0
  26. data/lib/source_monitor/setup/verification/runner.rb +24 -0
  27. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +75 -0
  28. data/lib/source_monitor/setup/verification/telemetry_logger.rb +33 -0
  29. data/lib/source_monitor/setup/workflow.rb +99 -0
  30. data/lib/source_monitor/version.rb +1 -1
  31. data/lib/source_monitor.rb +19 -0
  32. data/lib/tasks/source_monitor_setup.rake +39 -0
  33. data/tasks/prd-setup-workflow-streamlining.md +68 -0
  34. data/tasks/tasks-setup-workflow-streamlining.md +51 -0
  35. metadata +25 -2
  36. data/docs/installation.md +0 -144
@@ -0,0 +1,34 @@
1
+ require "thor"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class Prompter
6
+ def initialize(shell: Thor::Shell::Basic.new, auto_yes: false)
7
+ @shell = shell
8
+ @auto_yes = auto_yes
9
+ end
10
+
11
+ def ask(question, default: nil)
12
+ return default if auto_yes
13
+
14
+ prompt = default ? "#{question} [#{default}]" : question
15
+ response = shell.ask(prompt).to_s.strip
16
+ response.empty? ? default : response
17
+ end
18
+
19
+ def yes?(question, default: true)
20
+ return default if auto_yes
21
+
22
+ suffix = default ? "Y/n" : "y/N"
23
+ response = shell.ask("#{question} (#{suffix})").to_s.strip.downcase
24
+ return default if response.empty?
25
+
26
+ %w[y yes].include?(response)
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :shell, :auto_yes
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Requirements
4
+ class Version
5
+ attr_reader :requirement
6
+
7
+ def initialize(spec)
8
+ @requirement = Gem::Requirement.new(spec)
9
+ end
10
+
11
+ def expected
12
+ requirement.to_s
13
+ end
14
+
15
+ def normalize(value)
16
+ return if value.blank?
17
+
18
+ normalized = value.to_s.strip.sub(/^v/i, "")
19
+ Gem::Version.new(normalized)
20
+ rescue ArgumentError
21
+ nil
22
+ end
23
+
24
+ def satisfied?(value)
25
+ version = normalize(value)
26
+ return false unless version
27
+
28
+ requirement.satisfied_by?(version)
29
+ end
30
+ end
31
+
32
+ class Adapter
33
+ attr_reader :expected
34
+
35
+ def initialize(expected)
36
+ @expected = expected.to_s
37
+ end
38
+
39
+ def normalize(value)
40
+ value&.to_s
41
+ end
42
+
43
+ def satisfied?(value)
44
+ return false if value.blank?
45
+
46
+ value.to_s.casecmp(expected).zero?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ require "open3"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class ShellRunner
6
+ def run(*command)
7
+ stdout, status = Open3.capture2e(*command)
8
+ status.success? ? stdout.strip : nil
9
+ rescue Errno::ENOENT
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,79 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Verification
4
+ class ActionCableVerifier
5
+ def initialize(config: SourceMonitor.config.realtime, cable_config: default_cable_config, connection: default_connection)
6
+ @config = config
7
+ @cable_config = cable_config
8
+ @connection = connection
9
+ end
10
+
11
+ def call
12
+ case adapter
13
+ when :solid_cable
14
+ verify_solid_cable
15
+ when :redis
16
+ verify_redis
17
+ else
18
+ warning_result("Realtime adapter #{adapter.inspect} is not recognized", "Set config.realtime.adapter to :solid_cable or :redis in the initializer")
19
+ end
20
+ rescue StandardError => e
21
+ error_result("Action Cable verification failed: #{e.message}", "Double-check Action Cable configuration and credentials")
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :config, :cable_config, :connection
27
+
28
+ def adapter
29
+ config.adapter
30
+ end
31
+
32
+ def default_cable_config
33
+ ActionCable.server.config.cable
34
+ rescue StandardError
35
+ {}
36
+ end
37
+
38
+ def default_connection
39
+ ActiveRecord::Base.connection
40
+ rescue StandardError
41
+ nil
42
+ end
43
+
44
+ def verify_solid_cable
45
+ unless defined?(SolidCable)
46
+ return error_result("Solid Cable gem is not loaded", "Add `solid_cable` to your Gemfile or switch to the Redis adapter")
47
+ end
48
+
49
+ unless connection&.table_exists?("solid_cable_messages")
50
+ return error_result("Solid Cable tables are missing", "Run `rails solid_cable:install` or copy the engine migration that creates solid_cable_messages")
51
+ end
52
+
53
+ ok_result("Solid Cable tables detected and the gem is loaded")
54
+ end
55
+
56
+ def verify_redis
57
+ url = config.redis_url.presence || cable_config[:url]
58
+ if url.blank?
59
+ return error_result("Redis adapter configured without a URL", "Set config.realtime.redis_url or supply :url in your cable.yml")
60
+ end
61
+
62
+ ok_result("Redis Action Cable configuration detected (#{url})")
63
+ end
64
+
65
+ def ok_result(details)
66
+ Result.new(key: :action_cable, name: "Action Cable", status: :ok, details: details)
67
+ end
68
+
69
+ def warning_result(details, remediation)
70
+ Result.new(key: :action_cable, name: "Action Cable", status: :warning, details: details, remediation: remediation)
71
+ end
72
+
73
+ def error_result(details, remediation)
74
+ Result.new(key: :action_cable, name: "Action Cable", status: :error, details: details, remediation: remediation)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,24 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Verification
4
+ class Printer
5
+ def initialize(shell: Thor::Shell::Basic.new)
6
+ @shell = shell
7
+ end
8
+
9
+ def print(summary)
10
+ shell.say("Verification summary (#{summary.overall_status.upcase}):")
11
+ summary.results.each do |result|
12
+ shell.say("- #{result.name}: #{result.status.upcase} - #{result.details}")
13
+ shell.say(" Remediation: #{result.remediation}") if result.remediation.present?
14
+ end
15
+ shell.say("JSON: #{summary.to_json}")
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :shell
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,68 @@
1
+ require "json"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ module Verification
6
+ Result = Struct.new(
7
+ :key,
8
+ :name,
9
+ :status,
10
+ :details,
11
+ :remediation,
12
+ keyword_init: true
13
+ ) do
14
+ def ok?
15
+ status == :ok
16
+ end
17
+
18
+ def warning?
19
+ status == :warning
20
+ end
21
+
22
+ def error?
23
+ status == :error
24
+ end
25
+
26
+ def as_json(_options = nil)
27
+ {
28
+ key: key,
29
+ name: name,
30
+ status: status,
31
+ details: details,
32
+ remediation: remediation
33
+ }
34
+ end
35
+ end
36
+
37
+ class Summary
38
+ attr_reader :results
39
+
40
+ def initialize(results)
41
+ @results = results
42
+ end
43
+
44
+ def overall_status
45
+ return :error if results.any?(&:error?)
46
+ return :warning if results.any?(&:warning?)
47
+
48
+ :ok
49
+ end
50
+
51
+ def ok?
52
+ overall_status == :ok
53
+ end
54
+
55
+ def to_h
56
+ {
57
+ overall_status: overall_status,
58
+ results: results.map(&:as_json)
59
+ }
60
+ end
61
+
62
+ def to_json(*args)
63
+ to_h.to_json(*args)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Verification
4
+ class Runner
5
+ def initialize(verifiers: default_verifiers)
6
+ @verifiers = verifiers
7
+ end
8
+
9
+ def call
10
+ results = verifiers.map { |verifier| verifier.call }
11
+ Summary.new(results)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :verifiers
17
+
18
+ def default_verifiers
19
+ [ SolidQueueVerifier.new, ActionCableVerifier.new ]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,75 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Verification
4
+ class SolidQueueVerifier
5
+ DEFAULT_HEARTBEAT_THRESHOLD = 2.minutes
6
+
7
+ def initialize(process_relation: default_process_relation, connection: default_connection, clock: -> { Time.current })
8
+ @process_relation = process_relation
9
+ @connection = connection
10
+ @clock = clock
11
+ end
12
+
13
+ def call
14
+ return missing_gem_result unless process_relation
15
+ return missing_tables_result unless tables_present?
16
+
17
+ recent = recent_workers?
18
+
19
+ if recent
20
+ ok_result("Solid Queue workers are reporting heartbeats")
21
+ else
22
+ warning_result("No Solid Queue workers have reported in the last #{DEFAULT_HEARTBEAT_THRESHOLD.inspect}", "Start a Solid Queue worker with `bin/rails solid_queue:start` and ensure it stays running")
23
+ end
24
+ rescue StandardError => e
25
+ error_result("Solid Queue verification failed: #{e.message}", "Verify Solid Queue migrations are up to date and workers can access the database")
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :process_relation, :connection, :clock
31
+
32
+ def default_process_relation
33
+ SolidQueue::Process if defined?(SolidQueue::Process)
34
+ end
35
+
36
+ def default_connection
37
+ SolidQueue::Process.connection if defined?(SolidQueue::Process)
38
+ rescue StandardError
39
+ nil
40
+ end
41
+
42
+ def tables_present?
43
+ return false unless connection
44
+
45
+ connection.table_exists?(process_relation.table_name)
46
+ end
47
+
48
+ def recent_workers?
49
+ cutoff = clock.call - DEFAULT_HEARTBEAT_THRESHOLD
50
+ process_relation.where("last_heartbeat_at >= ?", cutoff).exists?
51
+ end
52
+
53
+ def missing_gem_result
54
+ error_result("Solid Queue gem is not available", "Add `solid_queue` to your Gemfile and bundle install")
55
+ end
56
+
57
+ def missing_tables_result
58
+ error_result("Solid Queue tables are missing", "Run `rails solid_queue:install` or copy the engine's Solid Queue migration")
59
+ end
60
+
61
+ def ok_result(details)
62
+ Result.new(key: :solid_queue, name: "Solid Queue", status: :ok, details: details)
63
+ end
64
+
65
+ def warning_result(details, remediation)
66
+ Result.new(key: :solid_queue, name: "Solid Queue", status: :warning, details: details, remediation: remediation)
67
+ end
68
+
69
+ def error_result(details, remediation)
70
+ Result.new(key: :solid_queue, name: "Solid Queue", status: :error, details: details, remediation: remediation)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ module SourceMonitor
5
+ module Setup
6
+ module Verification
7
+ class TelemetryLogger
8
+ def initialize(path: nil)
9
+ @path = Pathname.new(path || default_path)
10
+ end
11
+
12
+ def log(summary)
13
+ FileUtils.mkdir_p(path.dirname)
14
+ path.open("a") do |file|
15
+ file.puts({ timestamp: Time.current.iso8601, payload: summary.to_h }.to_json)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :path
22
+
23
+ def default_path
24
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
25
+ Rails.root.join("log", "source_monitor_setup.log")
26
+ else
27
+ Pathname.new("log/source_monitor_setup.log")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,99 @@
1
+ require "pathname"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class Workflow
6
+ DEFAULT_MOUNT_PATH = "/source_monitor".freeze
7
+
8
+ class RequirementError < StandardError
9
+ attr_reader :summary
10
+
11
+ def initialize(summary)
12
+ @summary = summary
13
+ super(build_message)
14
+ end
15
+
16
+ private
17
+
18
+ def build_message
19
+ messages = summary.errors.map do |result|
20
+ "#{result.name}: #{result.remediation}".strip
21
+ end
22
+ "Setup requirements failed. #{messages.join(' ')}"
23
+ end
24
+ end
25
+
26
+ def initialize(
27
+ dependency_checker: DependencyChecker.new,
28
+ prompter: Prompter.new,
29
+ gemfile_editor: GemfileEditor.new,
30
+ bundle_installer: BundleInstaller.new,
31
+ node_installer: NodeInstaller.new,
32
+ install_generator: InstallGenerator.new,
33
+ migration_installer: MigrationInstaller.new,
34
+ initializer_patcher: InitializerPatcher.new,
35
+ devise_detector: method(:default_devise_detector),
36
+ verifier: Verification::Runner.new
37
+ )
38
+ @dependency_checker = dependency_checker
39
+ @prompter = prompter
40
+ @gemfile_editor = gemfile_editor
41
+ @bundle_installer = bundle_installer
42
+ @node_installer = node_installer
43
+ @install_generator = install_generator
44
+ @migration_installer = migration_installer
45
+ @initializer_patcher = initializer_patcher
46
+ @devise_detector = devise_detector
47
+ @verifier = verifier
48
+ end
49
+
50
+ def run
51
+ summary = dependency_checker.call
52
+ raise RequirementError, summary if summary.errors?
53
+
54
+ mount_path = prompter.ask("Mount SourceMonitor at which path?", default: DEFAULT_MOUNT_PATH)
55
+
56
+ gemfile_editor.ensure_entry
57
+ bundle_installer.install
58
+ node_installer.install_if_needed
59
+ install_generator.run(mount_path: mount_path)
60
+ migration_installer.install
61
+ initializer_patcher.ensure_navigation_hint(mount_path: mount_path)
62
+
63
+ if devise_available? && prompter.yes?("Wire Devise authentication hooks into SourceMonitor?", default: true)
64
+ initializer_patcher.ensure_devise_hooks
65
+ end
66
+
67
+ verifier.call
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :dependency_checker,
73
+ :prompter,
74
+ :gemfile_editor,
75
+ :bundle_installer,
76
+ :node_installer,
77
+ :install_generator,
78
+ :migration_installer,
79
+ :initializer_patcher,
80
+ :devise_detector,
81
+ :verifier
82
+
83
+ def devise_available?
84
+ !!devise_detector.call
85
+ end
86
+
87
+ def default_devise_detector
88
+ Gem.loaded_specs.key?("devise") || gemfile_mentions_devise?
89
+ end
90
+
91
+ def gemfile_mentions_devise?
92
+ gemfile = Pathname.new("Gemfile")
93
+ gemfile.exist? && gemfile.read.include?("devise")
94
+ rescue StandardError
95
+ false
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,3 @@
1
1
  module SourceMonitor
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -81,6 +81,25 @@ require "source_monitor/scheduler"
81
81
  require "source_monitor/items/item_creator"
82
82
  require "source_monitor/health"
83
83
  require "source_monitor/assets"
84
+ require "source_monitor/setup/requirements"
85
+ require "source_monitor/setup/shell_runner"
86
+ require "source_monitor/setup/detectors"
87
+ require "source_monitor/setup/dependency_checker"
88
+ require "source_monitor/setup/prompter"
89
+ require "source_monitor/setup/gemfile_editor"
90
+ require "source_monitor/setup/bundle_installer"
91
+ require "source_monitor/setup/node_installer"
92
+ require "source_monitor/setup/install_generator"
93
+ require "source_monitor/setup/migration_installer"
94
+ require "source_monitor/setup/initializer_patcher"
95
+ require "source_monitor/setup/verification/result"
96
+ require "source_monitor/setup/verification/solid_queue_verifier"
97
+ require "source_monitor/setup/verification/action_cable_verifier"
98
+ require "source_monitor/setup/verification/runner"
99
+ require "source_monitor/setup/verification/printer"
100
+ require "source_monitor/setup/verification/telemetry_logger"
101
+ require "source_monitor/setup/workflow"
102
+ require "source_monitor/setup/cli"
84
103
 
85
104
  module SourceMonitor
86
105
  class << self
@@ -0,0 +1,39 @@
1
+ namespace :source_monitor do
2
+ namespace :setup do
3
+ desc "Verify host dependencies before running the guided SourceMonitor installer"
4
+ task check: :environment do
5
+ summary = SourceMonitor::Setup::DependencyChecker.new.call
6
+
7
+ puts "SourceMonitor dependency check:" # rubocop:disable Rails/Output
8
+ summary.results.each do |result|
9
+ status = result.status.to_s.upcase
10
+ current = result.current ? result.current.to_s : "missing"
11
+ expected = result.expected || "n/a"
12
+ puts "- #{result.name}: #{status} (current: #{current}, required: #{expected})" # rubocop:disable Rails/Output
13
+ end
14
+
15
+ if summary.errors?
16
+ messages = summary.errors.map do |result|
17
+ "#{result.name}: #{result.remediation}"
18
+ end
19
+
20
+ raise "SourceMonitor setup requirements failed. #{messages.join(' ')}"
21
+ end
22
+ end
23
+
24
+ desc "Verify queue workers, Action Cable, and telemetry hooks"
25
+ task verify: :environment do
26
+ summary = SourceMonitor::Setup::Verification::Runner.new.call
27
+ printer = SourceMonitor::Setup::Verification::Printer.new
28
+ printer.print(summary)
29
+
30
+ if ENV["SOURCE_MONITOR_SETUP_TELEMETRY"].present?
31
+ SourceMonitor::Setup::Verification::TelemetryLogger.new.log(summary)
32
+ end
33
+
34
+ unless summary.ok?
35
+ raise "SourceMonitor setup verification failed. See output above for remediation steps."
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,68 @@
1
+ # Streamlined Setup Workflow for SourceMonitor Engine
2
+
3
+ ## 1. Introduction / Overview
4
+ Teams currently follow a long, error-prone checklist to integrate the SourceMonitor engine into either brand-new or existing Rails 8 hosts. Manual gem edits, generator invocations, migration copying, authentication wiring, and Solid Queue/Solid Cable verification create setup drift and delayed feedback when something fails. This PRD defines a streamlined workflow that combines authoritative checklists with guided automation (scripts/generators) so users can reliably install, mount, and validate the engine with minimal switching between docs and terminals. The primary goal is to reduce manual tasks and error risk when onboarding SourceMonitor into any Rails app that meets the core prerequisites.
5
+
6
+ ## 2. Goals
7
+ - Reduce time-to-first-successful SourceMonitor dashboard load (including background job + Action Cable verification) to under 15 minutes for both new and existing hosts.
8
+ - Provide a single guided setup command (or task) that performs/coordinates all feasible installation steps while surfacing next actions inline.
9
+ - Deliver updated documentation/checklists that stay in sync with the guided workflow and clearly call out remaining manual steps.
10
+ - Ensure Solid Queue/Solid Cable (with optional Redis Action Cable fallback) are validated automatically before marking setup complete.
11
+ - Instrument the workflow to capture pass/fail telemetry for each step (stored locally/logged) to aid support and future improvements.
12
+
13
+ ## 3. User Stories
14
+ - **New Rails app maintainer:** “As a developer bootstrapping a fresh Rails 8.0.2.1+ app, I want a single command that adds SourceMonitor, runs the install generator, copies migrations, and verifies Solid Queue so I can start monitoring feeds without memorizing every prerequisite.”
15
+ - **Existing app maintainer:** “As a developer integrating SourceMonitor into an established Rails codebase, I want the installer to detect what’s already configured (Devise, Solid Queue migrations, Action Cable adapter) and only prompt me for missing pieces.”
16
+ - **Release engineer:** “As the person responsible for the release checklist, I want deterministic logs from the setup workflow so I can confirm every environment (staging, prod) passes the same validation steps.”
17
+
18
+ ## 4. Functional Requirements
19
+ 1. Provide a new CLI entry point (rails source_monitor:setup`) that orchestrates the onboarding steps end-to-end, supporting both fresh and existing hosts.
20
+ 2. The setup command must:
21
+ - Detect prerequisite versions (Ruby ≥ 3.4.4, Rails ≥ 8.0.2.1, PostgreSQL, Node ≥ 18) and halt with actionable remediation steps when mismatched.
22
+ - Offer to append the `source_monitor` gem declaration if missing, then run `bundle install` (respecting rbenv) with progress output.
23
+ - Run `npm install` only if the host manages node tooling and dependencies changed.
24
+ - Invoke `source_monitor:install` with a user-confirmed mount path (default `/source_monitor`) and confirm the engine route is reachable.
25
+ - Copy migrations via `railties:install:migrations FROM=source_monitor`, deduplicate Solid Queue tables if already present, and run `db:migrate`.
26
+ - Create/patch `config/initializers/source_monitor.rb` with sensible defaults plus TODO comments for any unresolved configuration (Mission Control, custom queues, etc.).
27
+ - Offer guided prompts (Y/N) for optional Devise wiring, with snippets inserted only when the host uses Devise (detected via Bundler).
28
+ - Verify Solid Queue worker availability by optionally starting a transient worker or pinging existing processes, logging failures with remediation.
29
+ - Verify Action Cable by ensuring either Solid Cable tables exist or Redis adapter credentials are configured; provide auto-detection and optional smoke ping.
30
+ 3. Emit a structured summary (JSON + human-readable table) at the end of the setup run showing which steps succeeded, which need manual follow-up, and links to docs.
31
+ 4. Create an updated checklist document (living under `docs/setup.md`) that mirrors the automated steps, clarifies manual-only tasks (e.g., navigation link updates, Mission Control wiring), and references the new command.
32
+ 5. Expose a reusable verification task (e.g., `rails source_monitor:verify_install`) that can be run in CI to confirm migrations, queues, and Action Cable remain healthy after upgrades.
33
+ 6. Ensure the workflow supports Solid Queue/Solid Cable by default and optionally configures Redis for Action Cable when the host opts in (prompt + adapter switch helper).
34
+ 7. Add minimal telemetry hooks (log lines or optional webhook) so support teams can request the last setup report when debugging customer installs.
35
+ 8. Provide sample scripts or templates for integrating a basic Devise-backed system test (sign-in + visit mount path) but mark it as optional to keep scope aligned with must-have validations (engine + background job success).
36
+ 9. Document how to roll back the setup (e.g., remove gem, initializer, route) to encourage experimentation without risk.
37
+
38
+ ## 5. Non-Goals (Out of Scope)
39
+ - Supporting non-PostgreSQL databases or Rails versions below 8.0.2.1.
40
+ - Full automation of Devise or other authentication stacks (provide guidance and optional snippets only).
41
+ - Provisioning or managing Redis/Solid Queue infrastructure beyond verifying connectivity and migrations.
42
+ - Replacing the existing CI pipeline; the focus is on installation workflow, not release automation.
43
+ - Creating GUI installers or web-based setup wizards—command-line + documentation only.
44
+
45
+ ## 6. Design Considerations
46
+ - Provide dry-run and verbose flags so advanced users can inspect actions without modifying their apps.
47
+ - Structure setup steps as discrete service objects to enable reuse in future generators and tests.
48
+ - Store mount path and adapter decisions in `config/source_monitor.yml` to keep the initializer tidy.
49
+ - When Redis is selected for Action Cable, generate template credentials entries and highlight security requirements.
50
+
51
+ ## 7. Technical Considerations
52
+ - Rails generators for CLI interactions to reuse existing generator patterns and support prompts.
53
+ - Respect rbenv by invoking Ruby/Bundler via `ENV["RBENV_ROOT"]` shims (mirroring existing binstubs).
54
+ - Ensure idempotency: rerunning the setup command should detect completed steps and skip or re-verify without breaking the app.
55
+ - For migration deduplication, compare checksum filenames before copying to avoid duplicates in existing hosts.
56
+ - Telemetry/logging should default to local file output (e.g., `log/source_monitor_setup.log`) with an opt-in environment variable for remote reporting.
57
+
58
+ ## 8. Success Metrics
59
+ - ≥80% of beta users report completing setup in ≤15 minutes (collected via follow-up survey/logs).
60
+ - Support tickets related to initial installation drop by 50% quarter-over-quarter.
61
+ - Automated verification task adopted in CI for at least 3 internal environments within two weeks of release.
62
+ - Zero critical setup regressions reported within the first month after launch (monitored via issue tracker).
63
+
64
+ ## 9. Open Questions
65
+ - Should the setup command automatically create a nav link in host layouts, or simply print instructions?
66
+ - How should we detect whether `npm install` is necessary in hosts that vend their own asset pipeline (e.g., Propshaft, esbuild)?
67
+ - Do we need to version-gate the workflow for future Rails releases (8.1, 8.2), and how will we communicate incompatibilities?
68
+ - Is telemetry opt-in per run (flag) or opt-out (environment variable)?