source_monitor 0.3.0 → 0.3.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/sm-architecture/SKILL.md +233 -0
  3. data/.claude/skills/sm-architecture/reference/extraction-patterns.md +192 -0
  4. data/.claude/skills/sm-architecture/reference/module-map.md +194 -0
  5. data/.claude/skills/sm-configuration-setting/SKILL.md +264 -0
  6. data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +248 -0
  7. data/.claude/skills/sm-configuration-setting/reference/settings-pattern.md +297 -0
  8. data/.claude/skills/sm-configure/SKILL.md +153 -0
  9. data/.claude/skills/sm-configure/reference/configuration-reference.md +321 -0
  10. data/.claude/skills/sm-dashboard-widget/SKILL.md +344 -0
  11. data/.claude/skills/sm-dashboard-widget/reference/dashboard-patterns.md +304 -0
  12. data/.claude/skills/sm-domain-model/SKILL.md +188 -0
  13. data/.claude/skills/sm-domain-model/reference/model-graph.md +114 -0
  14. data/.claude/skills/sm-domain-model/reference/table-structure.md +348 -0
  15. data/.claude/skills/sm-engine-migration/SKILL.md +395 -0
  16. data/.claude/skills/sm-engine-migration/reference/migration-conventions.md +255 -0
  17. data/.claude/skills/sm-engine-test/SKILL.md +302 -0
  18. data/.claude/skills/sm-engine-test/reference/test-helpers.md +259 -0
  19. data/.claude/skills/sm-engine-test/reference/test-patterns.md +411 -0
  20. data/.claude/skills/sm-event-handler/SKILL.md +265 -0
  21. data/.claude/skills/sm-event-handler/reference/events-api.md +229 -0
  22. data/.claude/skills/sm-health-rule/SKILL.md +327 -0
  23. data/.claude/skills/sm-health-rule/reference/health-system.md +269 -0
  24. data/.claude/skills/sm-host-setup/SKILL.md +223 -0
  25. data/.claude/skills/sm-host-setup/reference/initializer-template.md +195 -0
  26. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +134 -0
  27. data/.claude/skills/sm-job/SKILL.md +263 -0
  28. data/.claude/skills/sm-job/reference/job-conventions.md +245 -0
  29. data/.claude/skills/sm-model-extension/SKILL.md +287 -0
  30. data/.claude/skills/sm-model-extension/reference/extension-api.md +317 -0
  31. data/.claude/skills/sm-pipeline-stage/SKILL.md +254 -0
  32. data/.claude/skills/sm-pipeline-stage/reference/completion-handlers.md +152 -0
  33. data/.claude/skills/sm-pipeline-stage/reference/entry-processing.md +191 -0
  34. data/.claude/skills/sm-pipeline-stage/reference/feed-fetcher-architecture.md +198 -0
  35. data/.claude/skills/sm-scraper-adapter/SKILL.md +284 -0
  36. data/.claude/skills/sm-scraper-adapter/reference/adapter-contract.md +167 -0
  37. data/.claude/skills/sm-scraper-adapter/reference/example-adapter.md +274 -0
  38. data/.vbw-planning/.notification-log.jsonl +102 -0
  39. data/.vbw-planning/.session-log.jsonl +505 -0
  40. data/AGENTS.md +20 -57
  41. data/CHANGELOG.md +19 -0
  42. data/CLAUDE.md +44 -1
  43. data/CONTRIBUTING.md +5 -5
  44. data/Gemfile.lock +20 -21
  45. data/README.md +18 -5
  46. data/VERSION +1 -0
  47. data/docs/deployment.md +1 -1
  48. data/docs/setup.md +4 -4
  49. data/lib/source_monitor/setup/skills_installer.rb +94 -0
  50. data/lib/source_monitor/setup/workflow.rb +17 -2
  51. data/lib/source_monitor/version.rb +1 -1
  52. data/lib/tasks/source_monitor_setup.rake +58 -0
  53. data/source_monitor.gemspec +1 -0
  54. metadata +39 -1
@@ -0,0 +1,269 @@
1
+ # Health System Reference
2
+
3
+ ## System Registration
4
+
5
+ The health system registers itself during engine initialization via `Health.setup!`:
6
+
7
+ ```ruby
8
+ module SourceMonitor
9
+ module Health
10
+ module_function
11
+
12
+ def setup!
13
+ register_fetch_callback
14
+ end
15
+
16
+ def fetch_callback
17
+ @fetch_callback ||= lambda do |event|
18
+ source = event&.source
19
+ next unless source
20
+ SourceHealthMonitor.new(source: source).call
21
+ rescue StandardError => error
22
+ log_error(source, error)
23
+ end
24
+ end
25
+
26
+ def register_fetch_callback
27
+ callbacks = SourceMonitor.config.events.callbacks_for(:after_fetch_completed)
28
+ return if callbacks.include?(fetch_callback)
29
+ SourceMonitor.config.events.after_fetch_completed(fetch_callback)
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ Key behaviors:
36
+ - Uses a memoized lambda to prevent duplicate registration
37
+ - Checks existing callbacks before adding
38
+ - Errors in the health callback are logged but don't crash the fetch pipeline
39
+
40
+ ## SourceHealthMonitor Details
41
+
42
+ ### Rolling Success Rate Calculation
43
+
44
+ ```ruby
45
+ def calculate_success_rate(logs)
46
+ successes = logs.count { |log| log.success? }
47
+ total = logs.size
48
+ return 0.0 if total.zero?
49
+ (successes.to_f / total).round(4)
50
+ end
51
+ ```
52
+
53
+ The rate is stored as a float (0.0 to 1.0) on `source.rolling_success_rate`.
54
+
55
+ ### Minimum Sample Size
56
+
57
+ Thresholds only apply when the number of logs equals the configured `window_size`. This prevents premature auto-pausing of new sources:
58
+
59
+ ```ruby
60
+ def thresholds_applicable?(sample_size)
61
+ sample_size >= minimum_sample_size
62
+ end
63
+
64
+ def minimum_sample_size
65
+ [config.window_size.to_i, 1].max
66
+ end
67
+ ```
68
+
69
+ ### Auto-Pause Algorithm
70
+
71
+ ```
72
+ IF rate < auto_pause_threshold AND thresholds_active:
73
+ new_until = now + auto_pause_cooldown_minutes
74
+ IF existing_until > new_until:
75
+ keep existing_until (don't shorten pause)
76
+ ELSE:
77
+ use new_until
78
+
79
+ SET auto_paused_until = new_until
80
+ SET auto_paused_at = now (or keep existing)
81
+ PUSH next_fetch_at past pause window
82
+ PUSH backoff_until past pause window
83
+ ```
84
+
85
+ ### Auto-Resume Algorithm
86
+
87
+ ```
88
+ IF auto_paused_until IS NOT NULL AND rate >= auto_resume_threshold:
89
+ CLEAR auto_paused_until
90
+ CLEAR auto_paused_at
91
+ CLEAR backoff_until
92
+ ```
93
+
94
+ The resume threshold defaults to `max(auto_resume_threshold, auto_pause_threshold)` to prevent flapping.
95
+
96
+ ### Consecutive Failure Detection
97
+
98
+ ```ruby
99
+ def consecutive_failures(logs)
100
+ logs.take_while { |log| !log_success?(log) }.size
101
+ end
102
+ ```
103
+
104
+ Logs are ordered by `started_at DESC`, so `take_while` counts the most recent consecutive failures.
105
+
106
+ ### Improving Streak Detection
107
+
108
+ ```ruby
109
+ def improving_streak?(logs)
110
+ success_streak = 0
111
+ failure_seen = false
112
+ logs.each do |log|
113
+ if log_success?(log)
114
+ success_streak += 1
115
+ else
116
+ failure_seen = true
117
+ break
118
+ end
119
+ end
120
+ success_streak >= 2 && failure_seen
121
+ end
122
+ ```
123
+
124
+ A source is "improving" when it has >= 2 consecutive recent successes AND at least one failure exists in the window.
125
+
126
+ ### Fixed Interval Enforcement
127
+
128
+ For non-adaptive sources, the monitor ensures `backoff_until` doesn't persist beyond what's needed:
129
+
130
+ ```ruby
131
+ def enforce_fixed_interval(attrs, auto_paused_until)
132
+ return if source.adaptive_fetching_enabled?
133
+ return if auto_paused_active?(auto_paused_until)
134
+
135
+ backoff_value = attrs.key?(:backoff_until) ? attrs[:backoff_until] : source.backoff_until
136
+ return if backoff_value.blank?
137
+
138
+ fixed_minutes = [source.fetch_interval_minutes.to_i, 1].max
139
+ attrs[:next_fetch_at] = now + fixed_minutes.minutes
140
+ attrs[:backoff_until] = nil
141
+ end
142
+ ```
143
+
144
+ ## Circuit Breaker vs Auto-Pause
145
+
146
+ These are two separate protection mechanisms:
147
+
148
+ | Feature | Circuit Breaker | Auto-Pause |
149
+ |---------|----------------|------------|
150
+ | **Scope** | Single fetch attempt | Rolling window |
151
+ | **Trigger** | RetryPolicy exhaustion | Success rate threshold |
152
+ | **Duration** | Error-type specific (1-2 hours) | Configurable cooldown (60 min default) |
153
+ | **Fields** | `fetch_circuit_*` | `auto_paused_*` |
154
+ | **Managed by** | RetryPolicy + SourceUpdater | SourceHealthMonitor |
155
+ | **Resets on** | Successful fetch | Success rate recovery |
156
+
157
+ ### Circuit Breaker Flow
158
+
159
+ ```
160
+ Error occurs -> RetryPolicy.decision
161
+ -> retry? (attempts remaining)
162
+ -> schedule retry with wait
163
+ -> open_circuit? (attempts exhausted)
164
+ -> set fetch_circuit_until
165
+ -> FetchRunner skips fetch while circuit open
166
+ ```
167
+
168
+ ### Auto-Pause Flow
169
+
170
+ ```
171
+ After every fetch -> SourceHealthMonitor.call
172
+ -> calculate rolling success rate
173
+ -> IF rate < threshold AND window full
174
+ -> set auto_paused_until
175
+ -> push next_fetch_at past pause window
176
+ ```
177
+
178
+ ## HealthCheckLog Model
179
+
180
+ ```ruby
181
+ class HealthCheckLog < ApplicationRecord
182
+ include SourceMonitor::Loggable
183
+
184
+ belongs_to :source
185
+ has_one :log_entry, as: :loggable, dependent: :destroy
186
+
187
+ attribute :http_response_headers, default: -> { {} }
188
+ validates :source, presence: true
189
+
190
+ after_save :sync_log_entry # Creates unified LogEntry record
191
+ end
192
+ ```
193
+
194
+ Fields:
195
+ - `source_id` -- associated source
196
+ - `success` -- boolean
197
+ - `started_at`, `completed_at` -- timing
198
+ - `duration_ms` -- request duration
199
+ - `http_status` -- response status code
200
+ - `http_response_headers` -- response headers hash
201
+ - `error_class`, `error_message` -- error details
202
+
203
+ ## Source Model Health Fields
204
+
205
+ From migration `20251012090000_add_health_fields_to_sources`:
206
+
207
+ ```ruby
208
+ # Health status tracking
209
+ :health_status # string, default: "healthy"
210
+ :health_status_changed_at # datetime
211
+ :rolling_success_rate # float
212
+ :health_auto_pause_threshold # float (per-source override)
213
+
214
+ # Auto-pause state
215
+ :auto_paused_at # datetime
216
+ :auto_paused_until # datetime
217
+
218
+ # Circuit breaker state (fetch-level)
219
+ :fetch_retry_attempt # integer, default: 0
220
+ :fetch_circuit_opened_at # datetime
221
+ :fetch_circuit_until # datetime
222
+
223
+ # Existing fields used by health system
224
+ :failure_count # integer
225
+ :last_error # string
226
+ :last_error_at # datetime
227
+ :backoff_until # datetime
228
+ :next_fetch_at # datetime
229
+ :fetch_status # string
230
+ ```
231
+
232
+ ## Source Model Health Methods
233
+
234
+ ```ruby
235
+ def fetch_circuit_open?
236
+ fetch_circuit_until.present? && fetch_circuit_until.future?
237
+ end
238
+
239
+ def auto_paused?
240
+ auto_paused_until.present? && auto_paused_until.future?
241
+ end
242
+
243
+ def fetch_retry_attempt
244
+ value = super
245
+ value.present? ? value : 0
246
+ end
247
+ ```
248
+
249
+ ## Health Check Controllers
250
+
251
+ | Controller | Actions | Purpose |
252
+ |------------|---------|---------|
253
+ | `SourceHealthChecksController` | `create` | Trigger on-demand health check |
254
+ | `SourceHealthResetsController` | `create` | Reset all health state |
255
+ | `HealthController` | `show` | Engine health endpoint |
256
+
257
+ ## Import Source Health Check
258
+
259
+ Used during OPML import to validate feed URLs before importing:
260
+
261
+ ```ruby
262
+ result = Health::ImportSourceHealthCheck.new(feed_url: "https://example.com/feed.xml").call
263
+ ```
264
+
265
+ - No Source record needed (works with raw URL)
266
+ - No conditional headers (no prior state)
267
+ - Returns simple Result: `status`, `error_message`, `http_status`
268
+ - Used by `ImportSessionHealthCheckJob` for batch validation
269
+ - Results stored in `import_session.parsed_sources[n]["health_status"]`
@@ -0,0 +1,223 @@
1
+ ---
2
+ name: sm-host-setup
3
+ description: Use when setting up SourceMonitor in a host Rails application, including gem installation, engine mounting, migration copying, initializer creation, and verifying the install works.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # sm-host-setup: Host Application Setup
8
+
9
+ Guides integration of the SourceMonitor engine into a host Rails application.
10
+
11
+ ## When to Use
12
+
13
+ - Adding SourceMonitor to a new or existing Rails 8 host app
14
+ - Troubleshooting a broken installation
15
+ - Re-running setup after upgrading the gem
16
+ - Rolling back the engine from a host app
17
+
18
+ ## Prerequisites
19
+
20
+ | Requirement | Minimum | How to Check |
21
+ |---|---|---|
22
+ | Ruby | 3.4+ | `ruby -v` |
23
+ | Rails | 8.0+ | `bin/rails about` |
24
+ | PostgreSQL | 14+ | `psql --version` |
25
+ | Node.js | 18+ | `node -v` |
26
+ | Solid Queue | >= 0.3, < 3.0 | Check host Gemfile |
27
+ | Solid Cable or Redis | Solid Cable >= 3.0 | Check host Gemfile |
28
+
29
+ ## Installation Methods
30
+
31
+ ### Method 1: Guided CLI (Recommended)
32
+
33
+ The engine ships a guided installer that handles every step interactively.
34
+
35
+ ```bash
36
+ # From the host app root:
37
+ bundle add source_monitor --version "~> 0.3.0"
38
+ bundle install
39
+ bin/source_monitor install
40
+ ```
41
+
42
+ The guided workflow:
43
+ 1. Checks prerequisites via `DependencyChecker`
44
+ 2. Prompts for mount path (default: `/source_monitor`)
45
+ 3. Ensures `gem "source_monitor"` in Gemfile, runs `bundle install`
46
+ 4. Runs `npm install` if `package.json` exists
47
+ 5. Executes `bin/rails generate source_monitor:install --mount-path=...`
48
+ 6. Copies and deduplicates migrations, runs `bin/rails db:migrate`
49
+ 7. Patches the initializer with navigation hint and optional Devise hooks
50
+ 8. Runs verification and prints a report
51
+
52
+ Non-interactive mode:
53
+ ```bash
54
+ bin/source_monitor install --yes
55
+ ```
56
+
57
+ ### Method 2: Manual Step-by-Step
58
+
59
+ ```bash
60
+ # 1. Add the gem
61
+ gem "source_monitor", "~> 0.3.0" # in Gemfile
62
+ bundle install
63
+
64
+ # 2. Run the install generator
65
+ bin/rails generate source_monitor:install --mount-path=/source_monitor
66
+
67
+ # 3. Copy engine migrations
68
+ bin/rails railties:install:migrations FROM=source_monitor
69
+
70
+ # 4. Apply migrations
71
+ bin/rails db:migrate
72
+
73
+ # 5. Start background workers
74
+ bin/rails solid_queue:start
75
+
76
+ # 6. Verify
77
+ bin/source_monitor verify
78
+ ```
79
+
80
+ ### Method 3: GitHub Edge
81
+
82
+ ```ruby
83
+ # Gemfile
84
+ gem "source_monitor", github: "dchuk/source_monitor"
85
+ ```
86
+
87
+ ## What the Install Generator Does
88
+
89
+ The generator (`SourceMonitor::Generators::InstallGenerator`) performs two actions:
90
+
91
+ 1. **Mounts the engine** in `config/routes.rb`:
92
+ ```ruby
93
+ mount SourceMonitor::Engine, at: "/source_monitor"
94
+ ```
95
+ Skips if already mounted. The mount path is configurable via `--mount-path`.
96
+
97
+ 2. **Creates the initializer** at `config/initializers/source_monitor.rb`:
98
+ Uses the template at `lib/generators/source_monitor/install/templates/source_monitor.rb.tt`. Skips if the file already exists.
99
+
100
+ Re-running the generator is safe and idempotent.
101
+
102
+ ## Post-Install Configuration
103
+
104
+ After installation, review and customize the initializer. Key areas:
105
+
106
+ | Section | Purpose |
107
+ |---|---|
108
+ | Queue settings | Queue names, concurrency, namespace |
109
+ | Authentication | `authenticate_with`, `authorize_with` hooks |
110
+ | HTTP client | Timeouts, proxy, retries |
111
+ | Fetching | Adaptive scheduling intervals and factors |
112
+ | Health | Auto-pause/resume thresholds |
113
+ | Scrapers | Register custom scraper adapters |
114
+ | Retention | Item age limits and pruning strategy |
115
+ | Events | Lifecycle callbacks for integration |
116
+ | Models | Table prefix, concerns, validations |
117
+ | Realtime | Action Cable adapter (Solid Cable/Redis) |
118
+
119
+ See the `sm-configure` skill for full configuration reference.
120
+
121
+ ## Verification
122
+
123
+ ```bash
124
+ # CLI verification
125
+ bin/source_monitor verify
126
+
127
+ # Rake task verification
128
+ bin/rails source_monitor:setup:verify
129
+
130
+ # Prerequisite check only
131
+ bin/rails source_monitor:setup:check
132
+ ```
133
+
134
+ Verification checks Solid Queue workers and Action Cable health. Non-zero exit on failure.
135
+
136
+ Enable telemetry logging:
137
+ ```bash
138
+ SOURCE_MONITOR_SETUP_TELEMETRY=true bin/source_monitor verify
139
+ # Logs to log/source_monitor_setup.log
140
+ ```
141
+
142
+ ## Devise Integration
143
+
144
+ When Devise is detected, the guided installer offers to wire authentication hooks:
145
+
146
+ ```ruby
147
+ config.authentication.authenticate_with :authenticate_user!
148
+ config.authentication.authorize_with ->(controller) {
149
+ controller.current_user&.respond_to?(:admin?) ? controller.current_user.admin? : true
150
+ }
151
+ config.authentication.current_user_method = :current_user
152
+ config.authentication.user_signed_in_method = :user_signed_in?
153
+ ```
154
+
155
+ ## Rollback
156
+
157
+ 1. Remove `gem "source_monitor"` from Gemfile, `bundle install`
158
+ 2. Delete `config/initializers/source_monitor.rb`
159
+ 3. Remove `mount SourceMonitor::Engine` from `config/routes.rb`
160
+ 4. Drop tables: `bin/rails db:migrate:down VERSION=<timestamp>` for each engine migration
161
+ 5. Remove Solid Queue/Cable migrations only if no other components need them
162
+
163
+ ## Host Compatibility
164
+
165
+ | Scenario | Status |
166
+ |---|---|
167
+ | Rails 8 full-stack app | Supported |
168
+ | Rails 8 API-only app | Supported |
169
+ | Dedicated Solid Queue database | Supported |
170
+ | Redis-backed Action Cable | Supported |
171
+
172
+ ## Key Source Files
173
+
174
+ | File | Purpose |
175
+ |---|---|
176
+ | `lib/source_monitor/setup/workflow.rb` | Guided installer orchestration |
177
+ | `lib/generators/source_monitor/install/install_generator.rb` | Rails generator |
178
+ | `lib/generators/source_monitor/install/templates/source_monitor.rb.tt` | Initializer template |
179
+ | `lib/source_monitor/setup/initializer_patcher.rb` | Post-install patching |
180
+ | `lib/source_monitor/setup/verification/runner.rb` | Verification runner |
181
+ | `lib/source_monitor/engine.rb` | Engine configuration and initializers |
182
+ | `docs/setup.md` | Full setup documentation |
183
+ | `docs/troubleshooting.md` | Common fixes |
184
+
185
+ ## References
186
+
187
+ - `docs/setup.md` -- Complete setup workflow documentation
188
+ - `docs/configuration.md` -- Configuration reference
189
+ - `docs/troubleshooting.md` -- Common issues and fixes
190
+
191
+ ## Testing
192
+
193
+ After setup, verify with:
194
+ 1. `bin/source_monitor verify` -- Checks Solid Queue and Action Cable
195
+ 2. Visit the mount path in browser -- Dashboard should load
196
+ 3. Create a source and trigger "Fetch Now" -- Validates end-to-end
197
+
198
+ Optional system test for host apps using Devise:
199
+ ```ruby
200
+ # test/system/source_monitor_setup_test.rb
201
+ require "application_system_test_case"
202
+
203
+ class SourceMonitorSetupTest < ApplicationSystemTestCase
204
+ test "signed in admin can reach SourceMonitor" do
205
+ user = users(:admin)
206
+ sign_in user
207
+ visit "/source_monitor"
208
+ assert_text "SourceMonitor Dashboard"
209
+ end
210
+ end
211
+ ```
212
+
213
+ ## Checklist
214
+
215
+ - [ ] Ruby 3.4+, Rails 8.0+, PostgreSQL 14+ available
216
+ - [ ] `gem "source_monitor"` added to Gemfile
217
+ - [ ] `bundle install` completed
218
+ - [ ] Install generator ran (`bin/rails generate source_monitor:install`)
219
+ - [ ] Engine migrations copied and applied
220
+ - [ ] Solid Queue workers started
221
+ - [ ] Authentication hooks configured in initializer
222
+ - [ ] `bin/source_monitor verify` passes
223
+ - [ ] Dashboard accessible at mount path
@@ -0,0 +1,195 @@
1
+ # Initializer Template Reference
2
+
3
+ Complete annotated initializer for `config/initializers/source_monitor.rb`.
4
+
5
+ The install generator creates this file from `lib/generators/source_monitor/install/templates/source_monitor.rb.tt`. This reference expands on every option.
6
+
7
+ ## Full Template
8
+
9
+ ```ruby
10
+ # frozen_string_literal: true
11
+
12
+ # SourceMonitor engine configuration.
13
+ #
14
+ # These values default to conservative settings that work for most hosts.
15
+ # Tweak them here instead of monkey-patching the engine so upgrades remain easy.
16
+ # Restart the application after changes.
17
+ SourceMonitor.configure do |config|
18
+
19
+ # ===========================================================================
20
+ # Queue & Worker Settings
21
+ # ===========================================================================
22
+
23
+ # Namespace prefix for queue names and instrumentation keys.
24
+ # Combined with ActiveJob.queue_name_prefix automatically.
25
+ config.queue_namespace = "source_monitor"
26
+
27
+ # Dedicated queue names. Must match entries in config/solid_queue.yml.
28
+ config.fetch_queue_name = "source_monitor_fetch"
29
+ config.scrape_queue_name = "source_monitor_scrape"
30
+
31
+ # Worker concurrency per queue (advisory for Solid Queue).
32
+ config.fetch_queue_concurrency = 2
33
+ config.scrape_queue_concurrency = 2
34
+
35
+ # Override the job class Solid Queue uses for recurring "command" tasks.
36
+ # config.recurring_command_job_class = "MyRecurringCommandJob"
37
+
38
+ # Toggle lightweight queue metrics on the dashboard.
39
+ config.job_metrics_enabled = true
40
+
41
+ # ===========================================================================
42
+ # Mission Control Integration
43
+ # ===========================================================================
44
+
45
+ # Show "Open Mission Control" link on the SourceMonitor dashboard.
46
+ config.mission_control_enabled = false
47
+
48
+ # String path, route helper lambda, or nil.
49
+ # config.mission_control_dashboard_path = "/mission_control"
50
+ # config.mission_control_dashboard_path = -> {
51
+ # Rails.application.routes.url_helpers.mission_control_jobs_path
52
+ # }
53
+ config.mission_control_dashboard_path = nil
54
+
55
+ # ===========================================================================
56
+ # Authentication
57
+ # ===========================================================================
58
+ # Handlers: Symbol (invoked on controller) or callable (receives controller).
59
+
60
+ # Authenticate before accessing any SourceMonitor page.
61
+ # config.authentication.authenticate_with :authenticate_user!
62
+
63
+ # Authorize after authentication (return false or raise to deny).
64
+ # config.authentication.authorize_with ->(controller) {
65
+ # controller.current_user&.admin?
66
+ # }
67
+
68
+ # Method names SourceMonitor uses to access current user info.
69
+ # config.authentication.current_user_method = :current_user
70
+ # config.authentication.user_signed_in_method = :user_signed_in?
71
+
72
+ # ===========================================================================
73
+ # HTTP Client
74
+ # ===========================================================================
75
+ # Maps to Faraday middleware options.
76
+
77
+ config.http.timeout = 15 # Total request timeout (seconds)
78
+ config.http.open_timeout = 5 # Connection open timeout (seconds)
79
+ config.http.max_redirects = 5 # Max redirects to follow
80
+ # config.http.user_agent = "SourceMonitor/#{SourceMonitor::VERSION}"
81
+ # config.http.proxy = ENV["SOURCE_MONITOR_HTTP_PROXY"]
82
+ # config.http.headers = { "X-Request-ID" => -> { SecureRandom.uuid } }
83
+
84
+ # Retry settings (mapped to faraday-retry)
85
+ # config.http.retry_max = 4
86
+ # config.http.retry_interval = 0.5
87
+ # config.http.retry_interval_randomness = 0.5
88
+ # config.http.retry_backoff_factor = 2
89
+ # config.http.retry_statuses = [429, 500, 502, 503, 504]
90
+
91
+ # ===========================================================================
92
+ # Adaptive Fetch Scheduling
93
+ # ===========================================================================
94
+
95
+ # config.fetching.min_interval_minutes = 5 # Floor (default: 5 min)
96
+ # config.fetching.max_interval_minutes = 1440 # Ceiling (default: 24 hrs)
97
+ # config.fetching.increase_factor = 1.25 # Multiplier when no new items
98
+ # config.fetching.decrease_factor = 0.75 # Multiplier when items arrive
99
+ # config.fetching.failure_increase_factor = 1.5 # Multiplier on errors
100
+ # config.fetching.jitter_percent = 0.1 # Random jitter (+/-10%)
101
+
102
+ # ===========================================================================
103
+ # Source Health Monitoring
104
+ # ===========================================================================
105
+
106
+ config.health.window_size = 20 # Fetch attempts to evaluate
107
+ config.health.healthy_threshold = 0.8 # Ratio for "healthy" badge
108
+ config.health.warning_threshold = 0.5 # Ratio for "warning" badge
109
+ config.health.auto_pause_threshold = 0.2 # Auto-pause below this
110
+ config.health.auto_resume_threshold = 0.6 # Auto-resume above this
111
+ config.health.auto_pause_cooldown_minutes = 60 # Grace period before re-enable
112
+
113
+ # ===========================================================================
114
+ # Scraper Adapters
115
+ # ===========================================================================
116
+ # Must inherit from SourceMonitor::Scrapers::Base.
117
+
118
+ # config.scrapers.register(:custom, "MyApp::Scrapers::CustomAdapter")
119
+ # config.scrapers.unregister(:readability) # Remove built-in
120
+
121
+ # ===========================================================================
122
+ # Retention Defaults
123
+ # ===========================================================================
124
+ # Sources inherit these when their retention fields are blank.
125
+
126
+ config.retention.items_retention_days = nil # nil = retain forever
127
+ config.retention.max_items = nil # nil = unlimited
128
+ # config.retention.strategy = :destroy # or :soft_delete
129
+
130
+ # ===========================================================================
131
+ # Scraping Controls
132
+ # ===========================================================================
133
+
134
+ # config.scraping.max_in_flight_per_source = 25 # Concurrent scrapes per source
135
+ # config.scraping.max_bulk_batch_size = 100 # Max bulk enqueue size
136
+
137
+ # ===========================================================================
138
+ # Event Callbacks
139
+ # ===========================================================================
140
+ # Handlers receive a single event struct.
141
+
142
+ # config.events.after_item_created do |event|
143
+ # # event.item, event.source, event.entry, event.result, event.status
144
+ # NewItemNotifier.publish(event.item, source: event.source)
145
+ # end
146
+
147
+ # config.events.after_item_scraped do |event|
148
+ # # event.item, event.source, event.result, event.log, event.status
149
+ # SearchIndexer.reindex(event.item) if event.success?
150
+ # end
151
+
152
+ # config.events.after_fetch_completed do |event|
153
+ # # event.source, event.result, event.status
154
+ # Rails.logger.info "Fetch for #{event.source.name}: #{event.status}"
155
+ # end
156
+
157
+ # config.events.register_item_processor ->(context) {
158
+ # # context.item, context.source, context.entry, context.result, context.status
159
+ # ItemIndexer.index(context.item)
160
+ # }
161
+
162
+ # ===========================================================================
163
+ # Model Extensions
164
+ # ===========================================================================
165
+
166
+ # Override default table name prefix (default: "sourcemon_").
167
+ # config.models.table_name_prefix = "sourcemon_"
168
+
169
+ # Include concerns to extend engine models.
170
+ # config.models.source.include_concern "MyApp::SourceMonitor::SourceExtensions"
171
+ # config.models.item.include_concern "MyApp::SourceMonitor::ItemExtensions"
172
+
173
+ # Register custom validations.
174
+ # config.models.source.validate :enforce_custom_rules
175
+ # config.models.source.validate ->(record) {
176
+ # record.errors.add(:base, "custom error") unless record.valid_for_my_app?
177
+ # }
178
+
179
+ # ===========================================================================
180
+ # Realtime (Action Cable) Adapter
181
+ # ===========================================================================
182
+
183
+ config.realtime.adapter = :solid_cable
184
+ # config.realtime.adapter = :redis
185
+ # config.realtime.redis_url = ENV.fetch("SOURCE_MONITOR_REDIS_URL", nil)
186
+
187
+ # Solid Cable tuning:
188
+ # config.realtime.solid_cable.polling_interval = "0.1.seconds"
189
+ # config.realtime.solid_cable.message_retention = "1.day"
190
+ # config.realtime.solid_cable.autotrim = true
191
+ # config.realtime.solid_cable.silence_polling = true
192
+ # config.realtime.solid_cable.use_skip_locked = true
193
+ # config.realtime.solid_cable.connects_to = { database: { writing: :cable } }
194
+ end
195
+ ```