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,264 @@
1
+ ---
2
+ name: sm-configuration-setting
3
+ description: How to add or modify configuration settings in the Source Monitor engine. Use when adding a new config option, modifying defaults, creating a new settings section, or understanding the configuration architecture.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Source Monitor Configuration Settings
8
+
9
+ ## Architecture Overview
10
+
11
+ The `SourceMonitor::Configuration` class was refactored from 655 lines into a lean 87-line orchestrator plus 12 extracted settings files. Each settings file follows a consistent pattern.
12
+
13
+ ```
14
+ lib/source_monitor/configuration.rb # Main class (87 lines)
15
+ lib/source_monitor/configuration/
16
+ authentication_settings.rb # Auth handlers
17
+ events.rb # Callbacks and processors
18
+ fetching_settings.rb # Adaptive interval tuning
19
+ health_settings.rb # Health monitoring thresholds
20
+ http_settings.rb # HTTP client defaults
21
+ model_definition.rb # Concern/validation injection
22
+ models.rb # Model definitions registry
23
+ realtime_settings.rb # ActionCable adapter config
24
+ retention_settings.rb # Item retention policies
25
+ scraper_registry.rb # Scraper adapter registry
26
+ scraping_settings.rb # Scraping concurrency limits
27
+ validation_definition.rb # Validation DSL
28
+ ```
29
+
30
+ ## How Configuration Works
31
+
32
+ ```ruby
33
+ # In host app initializer
34
+ SourceMonitor.configure do |config|
35
+ config.fetching.min_interval_minutes = 10
36
+ config.http.timeout = 30
37
+ config.retention.strategy = :soft_delete
38
+ end
39
+
40
+ # Access at runtime
41
+ SourceMonitor.config.fetching.min_interval_minutes # => 10
42
+ ```
43
+
44
+ The `config` object is a `SourceMonitor::Configuration` instance. Sub-sections are accessed via reader methods that return settings objects.
45
+
46
+ ## Adding a Setting to an Existing Section
47
+
48
+ ### Step 1: Identify the Settings File
49
+
50
+ | Setting Category | File | Class |
51
+ |-----------------|------|-------|
52
+ | HTTP client | `http_settings.rb` | `HTTPSettings` |
53
+ | Adaptive fetching | `fetching_settings.rb` | `FetchingSettings` |
54
+ | Health monitoring | `health_settings.rb` | `HealthSettings` |
55
+ | Scraping limits | `scraping_settings.rb` | `ScrapingSettings` |
56
+ | Item retention | `retention_settings.rb` | `RetentionSettings` |
57
+ | Realtime/cable | `realtime_settings.rb` | `RealtimeSettings` |
58
+ | Authentication | `authentication_settings.rb` | `AuthenticationSettings` |
59
+ | Events/callbacks | `events.rb` | `Events` |
60
+ | Scraper adapters | `scraper_registry.rb` | `ScraperRegistry` |
61
+ | Model extensions | `models.rb` / `model_definition.rb` | `Models` / `ModelDefinition` |
62
+
63
+ ### Step 2: Add the Attribute
64
+
65
+ ```ruby
66
+ # lib/source_monitor/configuration/fetching_settings.rb
67
+ class FetchingSettings
68
+ attr_accessor :min_interval_minutes,
69
+ :max_interval_minutes,
70
+ :increase_factor,
71
+ :decrease_factor,
72
+ :failure_increase_factor,
73
+ :jitter_percent,
74
+ :my_new_setting # <-- Add here
75
+
76
+ def reset!
77
+ @min_interval_minutes = 5
78
+ @max_interval_minutes = 24 * 60
79
+ @increase_factor = 1.25
80
+ @decrease_factor = 0.75
81
+ @failure_increase_factor = 1.5
82
+ @jitter_percent = 0.1
83
+ @my_new_setting = "default" # <-- Set default here
84
+ end
85
+ end
86
+ ```
87
+
88
+ ### Step 3: Write Tests
89
+
90
+ ```ruby
91
+ # test/lib/source_monitor/configuration_test.rb
92
+ test "my_new_setting has correct default" do
93
+ assert_equal "default", SourceMonitor.config.fetching.my_new_setting
94
+ end
95
+
96
+ test "my_new_setting can be overridden" do
97
+ SourceMonitor.configure do |config|
98
+ config.fetching.my_new_setting = "custom"
99
+ end
100
+ assert_equal "custom", SourceMonitor.config.fetching.my_new_setting
101
+ end
102
+ ```
103
+
104
+ ### Step 4: Verify Reset
105
+
106
+ Ensure `reset!` restores the default. The test suite calls `SourceMonitor.reset_configuration!` in setup, which recreates the entire Configuration object.
107
+
108
+ ## Adding a Setting with Validation
109
+
110
+ For settings that need input normalization or validation, use custom setters:
111
+
112
+ ```ruby
113
+ class ScrapingSettings
114
+ attr_accessor :max_in_flight_per_source, :max_bulk_batch_size
115
+
116
+ # Custom setter with normalization
117
+ def max_in_flight_per_source=(value)
118
+ @max_in_flight_per_source = normalize_numeric(value)
119
+ end
120
+
121
+ private
122
+
123
+ def normalize_numeric(value)
124
+ return nil if value.nil?
125
+ return nil if value == ""
126
+ integer = value.respond_to?(:to_i) ? value.to_i : value
127
+ integer.positive? ? integer : nil
128
+ end
129
+ end
130
+ ```
131
+
132
+ For enum-style settings with strict validation:
133
+
134
+ ```ruby
135
+ class RetentionSettings
136
+ def strategy=(value)
137
+ normalized = normalize_strategy(value)
138
+ @strategy = normalized unless normalized.nil?
139
+ end
140
+
141
+ private
142
+
143
+ def normalize_strategy(value)
144
+ return :destroy if value.nil?
145
+ if value.respond_to?(:to_sym)
146
+ candidate = value.to_sym
147
+ raise ArgumentError, "Invalid retention strategy #{value.inspect}" unless %i[destroy soft_delete].include?(candidate)
148
+ candidate
149
+ else
150
+ raise ArgumentError, "Invalid retention strategy #{value.inspect}"
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ ## Creating a New Settings Section
157
+
158
+ ### Step 1: Create the Settings Class
159
+
160
+ ```ruby
161
+ # lib/source_monitor/configuration/notifications_settings.rb
162
+ # frozen_string_literal: true
163
+
164
+ module SourceMonitor
165
+ class Configuration
166
+ class NotificationsSettings
167
+ attr_accessor :enabled, :channels, :throttle_seconds
168
+
169
+ def initialize
170
+ reset!
171
+ end
172
+
173
+ def reset!
174
+ @enabled = true
175
+ @channels = []
176
+ @throttle_seconds = 60
177
+ end
178
+ end
179
+ end
180
+ end
181
+ ```
182
+
183
+ ### Step 2: Register in Configuration
184
+
185
+ ```ruby
186
+ # lib/source_monitor/configuration.rb
187
+ require "source_monitor/configuration/notifications_settings"
188
+
189
+ class Configuration
190
+ attr_reader :http, :scrapers, :retention, :events, :models,
191
+ :realtime, :fetching, :health, :authentication, :scraping,
192
+ :notifications # <-- Add reader
193
+
194
+ def initialize
195
+ # ... existing initialization ...
196
+ @notifications = NotificationsSettings.new # <-- Initialize
197
+ end
198
+ end
199
+ ```
200
+
201
+ ### Step 3: Write Tests
202
+
203
+ ```ruby
204
+ test "notifications settings have correct defaults" do
205
+ settings = SourceMonitor.config.notifications
206
+ assert_equal true, settings.enabled
207
+ assert_equal [], settings.channels
208
+ assert_equal 60, settings.throttle_seconds
209
+ end
210
+
211
+ test "notifications settings can be configured" do
212
+ SourceMonitor.configure do |config|
213
+ config.notifications.enabled = false
214
+ config.notifications.channels = [:email, :slack]
215
+ config.notifications.throttle_seconds = 30
216
+ end
217
+
218
+ settings = SourceMonitor.config.notifications
219
+ assert_equal false, settings.enabled
220
+ assert_equal [:email, :slack], settings.channels
221
+ assert_equal 30, settings.throttle_seconds
222
+ end
223
+ ```
224
+
225
+ ## Patterns by Section Type
226
+
227
+ ### Simple Accessor Pattern (FetchingSettings, HealthSettings)
228
+
229
+ Plain `attr_accessor` with defaults in `reset!`. No validation.
230
+
231
+ ### Normalized Setter Pattern (ScrapingSettings)
232
+
233
+ Custom setter that normalizes input (strings to integers, negatives to nil).
234
+
235
+ ### Enum Setter Pattern (RetentionSettings, RealtimeSettings)
236
+
237
+ Custom setter that validates against an allowed list and raises `ArgumentError`.
238
+
239
+ ### Handler/Callback Pattern (AuthenticationSettings, Events)
240
+
241
+ Registration methods that accept symbols, lambdas, or blocks.
242
+
243
+ ### Registry Pattern (ScraperRegistry)
244
+
245
+ Named registration with lookup, unregistration, and enumeration.
246
+
247
+ ### Nested Object Pattern (RealtimeSettings::SolidCableOptions)
248
+
249
+ Sub-objects with their own `reset!` and `to_h` methods.
250
+
251
+ ## Testing Checklist
252
+
253
+ - [ ] Default value is correct
254
+ - [ ] Value can be overridden via `SourceMonitor.configure`
255
+ - [ ] `reset!` restores the default (tested via `SourceMonitor.reset_configuration!`)
256
+ - [ ] Validation raises `ArgumentError` for invalid values (if applicable)
257
+ - [ ] String/nil normalization works correctly (if applicable)
258
+ - [ ] Test file: `test/lib/source_monitor/configuration_test.rb`
259
+ - [ ] Run: `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/configuration_test.rb`
260
+
261
+ ## References
262
+
263
+ - [reference/settings-catalog.md](reference/settings-catalog.md) -- All settings sections with their attributes
264
+ - [reference/settings-pattern.md](reference/settings-pattern.md) -- Step-by-step pattern for adding settings
@@ -0,0 +1,248 @@
1
+ # Configuration Settings Catalog
2
+
3
+ All configuration sections with their attributes, defaults, and types.
4
+
5
+ ## Top-Level Attributes
6
+
7
+ **File:** `lib/source_monitor/configuration.rb`
8
+
9
+ | Attribute | Type | Default | Description |
10
+ |-----------|------|---------|-------------|
11
+ | `queue_namespace` | String | `"source_monitor"` | Namespace prefix for queue names |
12
+ | `fetch_queue_name` | String | `"source_monitor_fetch"` | Queue name for fetch jobs |
13
+ | `scrape_queue_name` | String | `"source_monitor_scrape"` | Queue name for scrape jobs |
14
+ | `fetch_queue_concurrency` | Integer | `2` | Max concurrent fetch workers |
15
+ | `scrape_queue_concurrency` | Integer | `2` | Max concurrent scrape workers |
16
+ | `recurring_command_job_class` | Class/nil | `nil` | Custom recurring job class |
17
+ | `job_metrics_enabled` | Boolean | `true` | Enable job metrics tracking |
18
+ | `mission_control_enabled` | Boolean | `false` | Enable Mission Control integration |
19
+ | `mission_control_dashboard_path` | String/Proc/nil | `nil` | Path or callable for Mission Control |
20
+
21
+ **Methods:**
22
+ - `queue_name_for(:fetch)` / `queue_name_for(:scrape)` -- Returns prefixed queue name
23
+ - `concurrency_for(:fetch)` / `concurrency_for(:scrape)` -- Returns concurrency limit
24
+
25
+ ---
26
+
27
+ ## HTTPSettings
28
+
29
+ **File:** `lib/source_monitor/configuration/http_settings.rb`
30
+
31
+ | Attribute | Type | Default | Description |
32
+ |-----------|------|---------|-------------|
33
+ | `timeout` | Integer | `15` | Total request timeout (seconds) |
34
+ | `open_timeout` | Integer | `5` | Connection open timeout (seconds) |
35
+ | `max_redirects` | Integer | `5` | Max HTTP redirects to follow |
36
+ | `user_agent` | String | `"SourceMonitor/<version>"` | User-Agent header value |
37
+ | `proxy` | String/nil | `nil` | HTTP proxy URL |
38
+ | `headers` | Hash | `{}` | Default HTTP headers |
39
+ | `retry_max` | Integer | `4` | Max retry attempts |
40
+ | `retry_interval` | Float | `0.5` | Base retry interval (seconds) |
41
+ | `retry_interval_randomness` | Float | `0.5` | Retry interval randomness factor |
42
+ | `retry_backoff_factor` | Integer | `2` | Exponential backoff multiplier |
43
+ | `retry_statuses` | Array/nil | `nil` | HTTP statuses to retry on |
44
+
45
+ Has `reset!` method.
46
+
47
+ ---
48
+
49
+ ## FetchingSettings
50
+
51
+ **File:** `lib/source_monitor/configuration/fetching_settings.rb`
52
+
53
+ | Attribute | Type | Default | Description |
54
+ |-----------|------|---------|-------------|
55
+ | `min_interval_minutes` | Integer | `5` | Minimum fetch interval |
56
+ | `max_interval_minutes` | Integer | `1440` (24h) | Maximum fetch interval |
57
+ | `increase_factor` | Float | `1.25` | Multiplier when content unchanged |
58
+ | `decrease_factor` | Float | `0.75` | Multiplier when content changed |
59
+ | `failure_increase_factor` | Float | `1.5` | Multiplier on fetch failure |
60
+ | `jitter_percent` | Float | `0.1` | Random jitter (10%) |
61
+
62
+ Has `reset!` method. All attributes are plain `attr_accessor`.
63
+
64
+ ---
65
+
66
+ ## HealthSettings
67
+
68
+ **File:** `lib/source_monitor/configuration/health_settings.rb`
69
+
70
+ | Attribute | Type | Default | Description |
71
+ |-----------|------|---------|-------------|
72
+ | `window_size` | Integer | `20` | Rolling window of fetches for health calc |
73
+ | `healthy_threshold` | Float | `0.8` | Success rate above = healthy |
74
+ | `warning_threshold` | Float | `0.5` | Success rate above = warning |
75
+ | `auto_pause_threshold` | Float | `0.2` | Success rate below = auto-pause |
76
+ | `auto_resume_threshold` | Float | `0.6` | Success rate above = auto-resume |
77
+ | `auto_pause_cooldown_minutes` | Integer | `60` | Cooldown before auto-resume check |
78
+
79
+ Has `reset!` method. All attributes are plain `attr_accessor`.
80
+
81
+ ---
82
+
83
+ ## ScrapingSettings
84
+
85
+ **File:** `lib/source_monitor/configuration/scraping_settings.rb`
86
+
87
+ | Attribute | Type | Default | Description |
88
+ |-----------|------|---------|-------------|
89
+ | `max_in_flight_per_source` | Integer/nil | `25` | Max concurrent scrape jobs per source |
90
+ | `max_bulk_batch_size` | Integer/nil | `100` | Max items in a bulk scrape batch |
91
+
92
+ Has `reset!` method. Custom setters normalize values:
93
+ - `nil` -> `nil`
94
+ - `""` -> `nil`
95
+ - `0` or negative -> `nil`
96
+ - String -> parsed integer (if positive)
97
+ - Positive integer -> kept as-is
98
+
99
+ ---
100
+
101
+ ## RetentionSettings
102
+
103
+ **File:** `lib/source_monitor/configuration/retention_settings.rb`
104
+
105
+ | Attribute | Type | Default | Description |
106
+ |-----------|------|---------|-------------|
107
+ | `items_retention_days` | Integer/nil | `nil` | Days to keep items (nil = forever) |
108
+ | `max_items` | Integer/nil | `nil` | Max items per source (nil = unlimited) |
109
+ | `strategy` | Symbol | `:destroy` | `:destroy` or `:soft_delete` |
110
+
111
+ No `reset!` method (defaults set in `initialize`). The `strategy=` setter validates against allowed values and raises `ArgumentError` for invalid input. Setting `nil` resets to `:destroy`.
112
+
113
+ ---
114
+
115
+ ## RealtimeSettings
116
+
117
+ **File:** `lib/source_monitor/configuration/realtime_settings.rb`
118
+
119
+ | Attribute | Type | Default | Description |
120
+ |-----------|------|---------|-------------|
121
+ | `adapter` | Symbol | `:solid_cable` | One of `:solid_cable`, `:redis`, `:async` |
122
+ | `redis_url` | String/nil | `nil` | Redis URL (for `:redis` adapter) |
123
+ | `solid_cable` | SolidCableOptions | (nested object) | Solid Cable options |
124
+
125
+ Has `reset!` method. The `adapter=` setter validates against `VALID_ADAPTERS`.
126
+
127
+ **SolidCableOptions:**
128
+
129
+ | Attribute | Type | Default |
130
+ |-----------|------|---------|
131
+ | `polling_interval` | String | `"0.1.seconds"` |
132
+ | `message_retention` | String | `"1.day"` |
133
+ | `autotrim` | Boolean | `true` |
134
+ | `silence_polling` | Boolean | `true` |
135
+ | `use_skip_locked` | Boolean | `true` |
136
+ | `trim_batch_size` | Integer/nil | `nil` |
137
+ | `connects_to` | Hash/nil | `nil` |
138
+
139
+ **Methods:**
140
+ - `action_cable_config` -- Returns hash suitable for ActionCable configuration
141
+ - `solid_cable=(hash)` -- Bulk-assign SolidCable options from a hash
142
+
143
+ ---
144
+
145
+ ## AuthenticationSettings
146
+
147
+ **File:** `lib/source_monitor/configuration/authentication_settings.rb`
148
+
149
+ | Attribute | Type | Default | Description |
150
+ |-----------|------|---------|-------------|
151
+ | `authenticate_handler` | Handler/nil | `nil` | Authentication handler |
152
+ | `authorize_handler` | Handler/nil | `nil` | Authorization handler |
153
+ | `current_user_method` | Symbol/nil | `nil` | Method name for current user |
154
+ | `user_signed_in_method` | Symbol/nil | `nil` | Method name for signed-in check |
155
+
156
+ Has `reset!` method.
157
+
158
+ **Methods:**
159
+ - `authenticate_with(handler = nil, &block)` -- Register authentication handler
160
+ - `authorize_with(handler = nil, &block)` -- Register authorization handler
161
+
162
+ Handler types: `:symbol` (method name), `:callable` (lambda/proc/block).
163
+
164
+ ---
165
+
166
+ ## Events
167
+
168
+ **File:** `lib/source_monitor/configuration/events.rb`
169
+
170
+ | Callback Key | Description |
171
+ |-------------|-------------|
172
+ | `after_item_created` | Fires after a new item is created from a feed entry |
173
+ | `after_item_scraped` | Fires after an item's content is scraped |
174
+ | `after_fetch_completed` | Fires after a feed fetch completes (success or failure) |
175
+
176
+ **Methods:**
177
+ - `after_item_created(handler = nil, &block)` -- Register callback
178
+ - `after_item_scraped(handler = nil, &block)` -- Register callback
179
+ - `after_fetch_completed(handler = nil, &block)` -- Register callback
180
+ - `register_item_processor(processor = nil, &block)` -- Register item processor
181
+ - `callbacks_for(name)` -- Returns array of callbacks (dup'd)
182
+ - `item_processors` -- Returns array of processors (dup'd)
183
+ - `reset!` -- Clears all callbacks and processors
184
+
185
+ All handlers must respond to `#call`. Raises `ArgumentError` otherwise.
186
+
187
+ ---
188
+
189
+ ## ScraperRegistry
190
+
191
+ **File:** `lib/source_monitor/configuration/scraper_registry.rb`
192
+
193
+ Enumerable registry of scraper adapters.
194
+
195
+ **Methods:**
196
+ - `register(name, adapter)` -- Register adapter class by name
197
+ - `unregister(name)` -- Remove adapter
198
+ - `adapter_for(name)` -- Look up adapter class by name
199
+ - `each` -- Iterate over `{name => adapter}` pairs
200
+
201
+ Names are normalized to lowercase alphanumeric + underscores. Adapter classes must inherit from `SourceMonitor::Scrapers::Base`.
202
+
203
+ ---
204
+
205
+ ## Models
206
+
207
+ **File:** `lib/source_monitor/configuration/models.rb`
208
+
209
+ | Attribute | Type | Default | Description |
210
+ |-----------|------|---------|-------------|
211
+ | `table_name_prefix` | String | `"sourcemon_"` | Database table prefix |
212
+
213
+ **Model Keys:** `source`, `item`, `fetch_log`, `scrape_log`, `health_check_log`, `item_content`, `log_entry`
214
+
215
+ Each key returns a `ModelDefinition` instance.
216
+
217
+ **Methods:**
218
+ - `for(name)` -- Returns `ModelDefinition` by name (raises on unknown)
219
+
220
+ ---
221
+
222
+ ## ModelDefinition
223
+
224
+ **File:** `lib/source_monitor/configuration/model_definition.rb`
225
+
226
+ **Methods:**
227
+ - `include_concern(concern = nil, &block)` -- Register a concern module
228
+ - `each_concern` -- Iterate over registered concerns (yields `[signature, resolved_module]`)
229
+ - `validate(handler = nil, **options, &block)` -- Register a custom validation
230
+ - `validations` -- Returns array of `ValidationDefinition` instances
231
+
232
+ Concerns can be: Module instance, String constant name, or anonymous block.
233
+ Validations can be: Symbol method name, String, lambda, or block.
234
+
235
+ ---
236
+
237
+ ## ValidationDefinition
238
+
239
+ **File:** `lib/source_monitor/configuration/validation_definition.rb`
240
+
241
+ | Attribute | Type | Description |
242
+ |-----------|------|-------------|
243
+ | `handler` | Symbol/Proc | The validation handler |
244
+ | `options` | Hash | Options hash (e.g., `{ if: :active? }`) |
245
+
246
+ **Methods:**
247
+ - `signature` -- Returns deduplication key
248
+ - `symbol?` -- Returns true if handler is a Symbol or String