source_monitor 0.7.0 → 0.8.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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +45 -22
  3. data/.claude/skills/sm-configure/SKILL.md +10 -1
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +44 -0
  5. data/.claude/skills/sm-host-setup/reference/initializer-template.md +17 -0
  6. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +2 -0
  7. data/.claude/skills/sm-job/reference/job-conventions.md +26 -0
  8. data/.claude/skills/sm-upgrade/reference/version-history.md +22 -0
  9. data/.gitignore +10 -0
  10. data/AGENTS.md +1 -1
  11. data/CHANGELOG.md +56 -0
  12. data/CLAUDE.md +11 -5
  13. data/Gemfile.lock +1 -1
  14. data/README.md +6 -4
  15. data/VERSION +1 -1
  16. data/app/assets/builds/source_monitor/application.css +43 -0
  17. data/app/assets/builds/source_monitor/application.js +127 -0
  18. data/app/assets/builds/source_monitor/application.js.map +3 -3
  19. data/app/assets/javascripts/source_monitor/application.js +2 -0
  20. data/app/assets/javascripts/source_monitor/controllers/notification_container_controller.js +138 -0
  21. data/app/assets/javascripts/source_monitor/controllers/notification_controller.js +11 -0
  22. data/app/controllers/source_monitor/source_favicon_fetches_controller.rb +38 -0
  23. data/app/controllers/source_monitor/sources_controller.rb +11 -0
  24. data/app/helpers/source_monitor/application_helper.rb +51 -0
  25. data/app/jobs/source_monitor/favicon_fetch_job.rb +71 -0
  26. data/app/jobs/source_monitor/import_opml_job.rb +9 -0
  27. data/app/jobs/source_monitor/source_health_check_job.rb +10 -0
  28. data/app/models/source_monitor/source.rb +2 -0
  29. data/app/views/layouts/source_monitor/application.html.erb +23 -2
  30. data/app/views/source_monitor/shared/_toast.html.erb +1 -0
  31. data/app/views/source_monitor/sources/_details.html.erb +34 -5
  32. data/app/views/source_monitor/sources/_row.html.erb +11 -6
  33. data/config/routes.rb +1 -0
  34. data/docs/configuration.md +1 -1
  35. data/docs/upgrade.md +22 -0
  36. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +15 -1
  37. data/lib/source_monitor/configuration/favicons_settings.rb +42 -0
  38. data/lib/source_monitor/configuration/http_settings.rb +1 -1
  39. data/lib/source_monitor/configuration/scraping_settings.rb +1 -1
  40. data/lib/source_monitor/configuration.rb +3 -1
  41. data/lib/source_monitor/favicons/discoverer.rb +196 -0
  42. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +21 -0
  43. data/lib/source_monitor/fetching/feed_fetcher.rb +1 -0
  44. data/lib/source_monitor/http.rb +5 -3
  45. data/lib/source_monitor/version.rb +1 -1
  46. data/lib/source_monitor.rb +4 -0
  47. data/lib/tasks/test_fast.rake +11 -0
  48. data/source_monitor.gemspec +1 -1
  49. metadata +7 -93
  50. data/.vbw-planning/PROJECT.md +0 -51
  51. data/.vbw-planning/ROADMAP.md +0 -32
  52. data/.vbw-planning/SHIPPED.md +0 -63
  53. data/.vbw-planning/STATE.md +0 -27
  54. data/.vbw-planning/codebase/ARCHITECTURE.md +0 -147
  55. data/.vbw-planning/codebase/CONCERNS.md +0 -99
  56. data/.vbw-planning/codebase/CONVENTIONS.md +0 -97
  57. data/.vbw-planning/codebase/DEPENDENCIES.md +0 -100
  58. data/.vbw-planning/codebase/INDEX.md +0 -86
  59. data/.vbw-planning/codebase/META.md +0 -42
  60. data/.vbw-planning/codebase/PATTERNS.md +0 -262
  61. data/.vbw-planning/codebase/STACK.md +0 -101
  62. data/.vbw-planning/codebase/STRUCTURE.md +0 -324
  63. data/.vbw-planning/codebase/TESTING.md +0 -154
  64. data/.vbw-planning/config.json +0 -53
  65. data/.vbw-planning/discovery.json +0 -26
  66. data/.vbw-planning/milestones/default/ROADMAP.md +0 -115
  67. data/.vbw-planning/milestones/default/STATE.md +0 -82
  68. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +0 -56
  69. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +0 -187
  70. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +0 -64
  71. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +0 -137
  72. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +0 -67
  73. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +0 -142
  74. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +0 -64
  75. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +0 -138
  76. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +0 -85
  77. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +0 -147
  78. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +0 -63
  79. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +0 -129
  80. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +0 -74
  81. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +0 -154
  82. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +0 -303
  83. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +0 -510
  84. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +0 -61
  85. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +0 -161
  86. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +0 -66
  87. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +0 -132
  88. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +0 -59
  89. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +0 -171
  90. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +0 -56
  91. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +0 -152
  92. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +0 -33
  93. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +0 -42
  94. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +0 -119
  95. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +0 -52
  96. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +0 -195
  97. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +0 -79
  98. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +0 -130
  99. data/.vbw-planning/milestones/generator-enhancements/REQUIREMENTS.md +0 -72
  100. data/.vbw-planning/milestones/generator-enhancements/ROADMAP.md +0 -125
  101. data/.vbw-planning/milestones/generator-enhancements/SHIPPED.md +0 -40
  102. data/.vbw-planning/milestones/generator-enhancements/STATE.md +0 -43
  103. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/01-CONTEXT.md +0 -33
  104. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/01-VERIFICATION.md +0 -86
  105. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/PLAN-01-SUMMARY.md +0 -61
  106. data/.vbw-planning/milestones/generator-enhancements/phases/01-generator-steps/PLAN-01.md +0 -380
  107. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/02-VERIFICATION.md +0 -78
  108. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/PLAN-01-SUMMARY.md +0 -46
  109. data/.vbw-planning/milestones/generator-enhancements/phases/02-verification/PLAN-01.md +0 -500
  110. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/03-VERIFICATION.md +0 -89
  111. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/PLAN-01-SUMMARY.md +0 -48
  112. data/.vbw-planning/milestones/generator-enhancements/phases/03-docs-alignment/PLAN-01.md +0 -456
  113. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/04-VERIFICATION.md +0 -129
  114. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/PLAN-01-SUMMARY.md +0 -70
  115. data/.vbw-planning/milestones/generator-enhancements/phases/04-dashboard-ux/PLAN-01.md +0 -747
  116. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/05-VERIFICATION.md +0 -156
  117. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-01-SUMMARY.md +0 -69
  118. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-01.md +0 -455
  119. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-02-SUMMARY.md +0 -39
  120. data/.vbw-planning/milestones/generator-enhancements/phases/05-active-storage-images/PLAN-02.md +0 -488
  121. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/06-VERIFICATION.md +0 -100
  122. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md +0 -37
  123. data/.vbw-planning/milestones/generator-enhancements/phases/06-netflix-feed-fix/PLAN-01.md +0 -345
  124. data/.vbw-planning/milestones/upgrade-assurance/REQUIREMENTS.md +0 -80
  125. data/.vbw-planning/milestones/upgrade-assurance/ROADMAP.md +0 -75
  126. data/.vbw-planning/milestones/upgrade-assurance/STATE.md +0 -29
  127. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/01-VERIFICATION.md +0 -144
  128. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01-SUMMARY.md +0 -43
  129. data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01.md +0 -405
  130. data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01-SUMMARY.md +0 -27
  131. data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01.md +0 -303
  132. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/03-VERIFICATION.md +0 -380
  133. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01-SUMMARY.md +0 -36
  134. data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01.md +0 -652
  135. data/.vbw-planning/phases/01-aia-certificate-resolution/.context-dev.md +0 -17
  136. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-01-SUMMARY.md +0 -26
  137. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-01.md +0 -71
  138. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-02-SUMMARY.md +0 -16
  139. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-02.md +0 -56
  140. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-03-SUMMARY.md +0 -17
  141. data/.vbw-planning/phases/01-aia-certificate-resolution/PLAN-03.md +0 -98
@@ -1,303 +0,0 @@
1
- ---
2
- phase: 2
3
- plan: "01"
4
- title: config-deprecation-framework
5
- type: execute
6
- wave: 1
7
- depends_on: []
8
- cross_phase_deps: [{phase: 1, plan: "01", artifact: "lib/source_monitor.rb", reason: "Autoload declarations pattern established in Phase 1"}]
9
- autonomous: true
10
- effort_override: thorough
11
- skills_used: [sm-configuration-setting, sm-engine-test]
12
- files_modified:
13
- - lib/source_monitor/configuration/deprecation_registry.rb
14
- - lib/source_monitor.rb
15
- - lib/source_monitor/configuration.rb
16
- - test/lib/source_monitor/configuration/deprecation_registry_test.rb
17
- must_haves:
18
- truths:
19
- - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/configuration/deprecation_registry_test.rb` exits 0 with 0 failures"
20
- - "Running `bin/rubocop lib/source_monitor/configuration/deprecation_registry.rb` exits 0 with no offenses"
21
- - "Running `bin/rails test` exits 0 with 992+ runs and 0 failures"
22
- - "Running `bin/rubocop` exits 0 with no offenses"
23
- - "SourceMonitor.configure with only valid current options produces zero deprecation warnings"
24
- artifacts:
25
- - path: "lib/source_monitor/configuration/deprecation_registry.rb"
26
- provides: "Deprecation registry that stores entries and checks config for deprecated option usage (REQ-28)"
27
- contains: "class DeprecationRegistry"
28
- - path: "lib/source_monitor/configuration.rb"
29
- provides: "Configuration#check_deprecations! called from SourceMonitor.configure"
30
- contains: "check_deprecations!"
31
- - path: "lib/source_monitor.rb"
32
- provides: "SourceMonitor.configure calls check_deprecations! after yielding config block"
33
- contains: "check_deprecations!"
34
- - path: "test/lib/source_monitor/configuration/deprecation_registry_test.rb"
35
- provides: "Tests covering all DeprecationRegistry branches including warning, error, no-op, and DSL"
36
- contains: "class DeprecationRegistryTest"
37
- key_links:
38
- - from: "deprecation_registry.rb"
39
- to: "REQ-28"
40
- via: "Stores deprecation entries and checks config object for stale/renamed/removed options"
41
- - from: "configuration.rb#check_deprecations!"
42
- to: "deprecation_registry.rb"
43
- via: "Configuration delegates deprecation checking to the registry"
44
- - from: "source_monitor.rb#configure"
45
- to: "configuration.rb#check_deprecations!"
46
- via: "configure method triggers deprecation check after block completes"
47
- ---
48
- <objective>
49
- Build a lightweight configuration deprecation framework (REQ-28) that warns host app developers when their initializer uses config options that have been renamed or removed. The framework provides a DeprecationRegistry DSL for engine developers to register deprecations, hooks into SourceMonitor.configure to scan after the config block completes, and produces actionable Rails.logger.warn messages for :warning severity or raises SourceMonitor::DeprecatedOptionError for :error severity. No real deprecations exist today -- the framework is infrastructure for future use, validated with synthetic test deprecations.
50
- </objective>
51
- <context>
52
- @lib/source_monitor/configuration.rb -- Main configuration class. Has attr_accessor for top-level options (queue_namespace, fetch_queue_name, etc.) and attr_reader for nested settings objects (http, scrapers, retention, events, models, realtime, fetching, health, authentication, scraping, images). The check_deprecations! method will be added here, delegating to DeprecationRegistry.check!(self). Key: deprecation paths use dot notation matching this structure -- e.g. "queue_namespace" for top-level, "http.timeout" for nested.
53
-
54
- @lib/source_monitor/configuration/http_settings.rb -- Example settings class pattern. Plain Ruby class with attr_accessor and reset!. Shows the structure that deprecation checking must traverse: each settings class is a separate object accessible via config.http, config.fetching, etc. The registry needs to resolve dot-notation paths by splitting on "." and walking the config object graph.
55
-
56
- @lib/source_monitor/configuration/fetching_settings.rb -- Another settings class showing the pattern: initialize calls reset!, attr_accessor for all fields. The deprecation framework does NOT modify these classes. It inspects them from outside by calling respond_to? and public_send.
57
-
58
- @lib/source_monitor/configuration/retention_settings.rb -- Settings class with custom setters (strategy=). Shows that some settings use custom writer methods rather than raw attr_accessor. The deprecation framework checks if an option was SET, not just if it exists. Approach: the registry stores the old option path and checks if the config object responds to that method. For renamed options, it checks if the old method exists (it should not on current config -- if it does, someone is calling it). For removed options, if someone references a method that no longer exists, Ruby will raise NoMethodError before our check runs. So the practical approach is: register deprecations with the OLD path, and during check!, attempt to resolve the path. If the path resolves (meaning the old option still exists as an accessor), log the warning. If it does not resolve, skip silently (the option was already removed, and any access would have raised NoMethodError naturally). For `:error` severity removed options, we use a different approach: install a method_missing trap or explicitly define a method that raises.
59
-
60
- **Simpler design decision:** The registry operates in two modes:
61
- 1. **Renamed options** (severity: :warning): The old option path still exists as an alias or the registry defines a getter that warns and delegates. But this adds complexity. Simpler: just scan a "deprecation log" that tracks which deprecated setters were called.
62
- 2. **Post-configure scan** (chosen approach): After the configure block runs, iterate all registered deprecations and check if the deprecated option was accessed. Since we cannot easily detect "was this setter called" without wrapping it, the simplest approach is:
63
- - For :warning (renamed): Register a `method_missing` or `define_method` on the relevant settings class that logs a warning and forwards to the new option.
64
- - For :error (removed): Register a `define_method` on the relevant settings class that raises.
65
- - Alternative: Use a tracking hash. When register is called, we dynamically define a setter on the settings class that records the access in the registry, then the post-configure check reports all recorded accesses.
66
-
67
- **Final design (simplest):** The DeprecationRegistry stores entries. When an entry is registered, it dynamically defines a method on the target settings class (or Configuration itself for top-level options) that:
68
- - For :warning -- logs via Rails.logger.warn, then forwards to the replacement option
69
- - For :error -- raises SourceMonitor::DeprecatedOptionError with the migration message
70
- This is clean because: (a) the method is defined once at registration time, (b) it fires when the host app actually calls the deprecated setter in their configure block, (c) no post-scan needed, (d) zero overhead for non-deprecated options.
71
-
72
- The `register` DSL: `DeprecationRegistry.register("http.proxy_url", removed_in: "0.5.0", replacement: "http.proxy", severity: :warning, message: "Use config.http.proxy instead")`. The registry parses the path, finds the settings class, and defines the trapping method.
73
-
74
- For testing, we register synthetic deprecations (e.g. a fake option "http.old_timeout") and verify that accessing it in a configure block triggers the warning/error.
75
-
76
- @lib/source_monitor.rb lines 197-217 -- The `configure` method yields config, then calls ModelExtensions.reload!. The deprecation framework hooks in here. Since we chose the define_method approach (methods fire on access, not post-scan), the configure method does NOT need a post-scan call. However, adding `config.check_deprecations!` as a post-configure hook is still useful as a safety net and for "removed option" detection. Actually, with the define_method approach, removed options raise immediately when accessed. So the only role for check_deprecations! is belt-and-suspenders. We can keep it simple: register defines methods, no post-scan needed. But for completeness and the "zero false positives" criterion, add a `check_deprecations!` that the configure method calls. This method iterates all :error entries and verifies the config is clean (no-op if no errors were raised, which is guaranteed by the define_method traps). Actually, let's just keep the define_method approach and skip the post-scan entirely. The `configure` method stays unchanged. The registry is self-contained.
77
-
78
- **Revised final design:** DeprecationRegistry is a class with class-level state (entries hash). `register` stores the entry and defines a method on the target class. `clear!` removes all registered deprecations and undefines the methods (for test isolation). `entries` returns the hash for inspection. No changes to `SourceMonitor.configure` needed -- the trapping methods fire during the configure block naturally. Add `check_deprecations!` to Configuration anyway for explicit post-configure validation (iterates entries, no-op for now, but extensible for future "default changed" checks).
79
-
80
- @test/lib/source_monitor/configuration_test.rb -- Existing configuration tests with setup/teardown that calls reset_configuration!. The deprecation registry test file follows the same pattern but also clears the registry in teardown. Tests use synthetic deprecations registered in setup, then verify warning/error behavior.
81
-
82
- @lib/source_monitor/configuration/scraping_settings.rb -- Shows custom setter pattern (normalize_numeric). If a deprecated option path targets a class that already has a custom setter, the registry-defined method must coexist. Since we define a NEW method (the old/deprecated name), there is no collision with existing methods.
83
- </context>
84
- <tasks>
85
- <task type="auto">
86
- <name>create-deprecation-registry</name>
87
- <files>
88
- lib/source_monitor/configuration/deprecation_registry.rb
89
- </files>
90
- <action>
91
- Create `lib/source_monitor/configuration/deprecation_registry.rb` with the DeprecationRegistry class.
92
-
93
- Module nesting: `SourceMonitor::Configuration::DeprecationRegistry`.
94
-
95
- Class-level state (use class instance variables, not class variables):
96
- - `@entries` -- Hash mapping `"path"` to entry hash `{ path:, removed_in:, replacement:, severity:, message: }`
97
- - `@defined_methods` -- Array of `[klass, method_name]` tuples for cleanup
98
-
99
- Class methods:
100
-
101
- **`register(path, removed_in:, replacement: nil, severity: :warning, message: nil)`**
102
- 1. Parse `path` -- split on ".". If one segment, target class is `Configuration`. If two segments, first is the settings accessor name, second is the deprecated option name.
103
- 2. Resolve target class: for "http.old_option", the target is `Configuration::HTTPSettings`. Use a mapping hash: `{ "http" => HTTPSettings, "fetching" => FetchingSettings, "health" => HealthSettings, "scraping" => ScrapingSettings, "retention" => RetentionSettings, "realtime" => RealtimeSettings, "authentication" => AuthenticationSettings, "images" => ImagesSettings, "scraper" => ScraperRegistry, "events" => Events, "models" => Models }`. For top-level, target is `Configuration`.
104
- 3. Build the deprecation message: `"[SourceMonitor] DEPRECATION: '#{path}' was deprecated in v#{removed_in}#{replacement_text}. #{message}"`. Where replacement_text is ` and replaced by '#{replacement}'` if replacement is present.
105
- 4. Define a writer method (`"#{option_name}="`) on the target class:
106
- - For `:warning` severity: the method logs via `Rails.logger.warn(deprecation_message)` and, if replacement is present, forwards the value to the replacement setter. If no replacement, the value is silently dropped.
107
- - For `:error` severity: the method raises `SourceMonitor::DeprecatedOptionError.new(deprecation_message)`.
108
- 5. Also define a reader method (`option_name`) for :warning that forwards to replacement getter, or for :error that raises.
109
- 6. Store the entry in `@entries` and record `[target_class, method_name]` in `@defined_methods`.
110
-
111
- **`clear!`**
112
- Remove all defined methods from their target classes (use `remove_method`), clear `@entries` and `@defined_methods`. This is essential for test isolation.
113
-
114
- **`entries`** -- returns `@entries.dup`
115
-
116
- **`registered?(path)`** -- returns boolean
117
-
118
- Also define `SourceMonitor::DeprecatedOptionError < StandardError` in this file.
119
-
120
- Key design points:
121
- - The SETTINGS_CLASSES mapping resolves settings accessor names to their Ruby classes.
122
- - `define_method` is used on the target class so the trap fires during normal configure block usage.
123
- - `remove_method` (not `undef_method`) is used in clear! so the class reverts to its original behavior.
124
- - Thread safety: registration happens at boot time (in an initializer or engine config), not at runtime. No mutex needed.
125
- - If the target class already responds to the deprecated method name, skip defining (the option is not actually removed/renamed yet -- this is a configuration error by the engine developer). Log a warning to stderr instead.
126
- </action>
127
- <verify>
128
- Read the created file. Confirm: (a) class is SourceMonitor::Configuration::DeprecationRegistry, (b) register method accepts path/removed_in/replacement/severity/message, (c) define_method on target class for both reader and writer, (d) :warning severity logs and forwards, (e) :error severity raises DeprecatedOptionError, (f) clear! removes defined methods and resets state, (g) SETTINGS_CLASSES mapping covers all 11 settings classes, (h) DeprecatedOptionError is defined. Run `bin/rubocop lib/source_monitor/configuration/deprecation_registry.rb` -- 0 offenses.
129
- </verify>
130
- <done>
131
- DeprecationRegistry class created with register/clear!/entries/registered? class methods. Defines trapping methods on target settings classes for both :warning and :error severities. DeprecatedOptionError defined. SETTINGS_CLASSES maps all 11 settings accessor names.
132
- </done>
133
- </task>
134
- <task type="auto">
135
- <name>wire-registry-into-configuration-and-autoload</name>
136
- <files>
137
- lib/source_monitor/configuration.rb
138
- lib/source_monitor.rb
139
- </files>
140
- <action>
141
- **Modify `lib/source_monitor/configuration.rb`:**
142
-
143
- Add a require at the top (after the existing requires, before the module definition):
144
- ```ruby
145
- require "source_monitor/configuration/deprecation_registry"
146
- ```
147
-
148
- Add a public method to Configuration:
149
- ```ruby
150
- def check_deprecations!
151
- DeprecationRegistry.check_defaults!(self)
152
- end
153
- ```
154
-
155
- This method is a hook point for future "default changed" checks. For now, DeprecationRegistry.check_defaults! is a no-op class method (define it in the registry). It exists so that future phases can add checks like "option X changed its default from A to B in version Y".
156
-
157
- **Modify `lib/source_monitor.rb`:**
158
-
159
- In the `configure` method (around line 198), add `config.check_deprecations!` after `yield config` and before `ModelExtensions.reload!`:
160
-
161
- ```ruby
162
- def configure
163
- yield config
164
- config.check_deprecations!
165
- SourceMonitor::ModelExtensions.reload!
166
- end
167
- ```
168
-
169
- Also in the `reset_configuration!` method, add `DeprecationRegistry.clear!` call to ensure test isolation works when resetting config:
170
-
171
- Actually, NO -- `clear!` should NOT be called on reset_configuration. The registry is global engine state (deprecations are registered once at boot), not per-configuration-instance state. Clearing on reset would break the deprecation framework. Instead, test isolation for registry tests should call `DeprecationRegistry.clear!` in their own teardown.
172
-
173
- So the only change to `reset_configuration!` is: nothing. Leave it as-is.
174
-
175
- Summary of changes:
176
- 1. `configuration.rb`: Add `require` for deprecation_registry, add `check_deprecations!` method
177
- 2. `source_monitor.rb`: Add `config.check_deprecations!` in `configure` method after yield
178
- </action>
179
- <verify>
180
- Read `lib/source_monitor/configuration.rb` -- confirm `require "source_monitor/configuration/deprecation_registry"` is present and `check_deprecations!` method exists. Read `lib/source_monitor.rb` -- confirm `config.check_deprecations!` is called in `configure` after yield. Run `bin/rubocop lib/source_monitor/configuration.rb lib/source_monitor.rb` -- 0 offenses.
181
- </verify>
182
- <done>
183
- DeprecationRegistry required from configuration.rb. check_deprecations! method added to Configuration class. SourceMonitor.configure calls check_deprecations! after yield. No changes to reset_configuration! (registry state is global, not per-instance).
184
- </done>
185
- </task>
186
- <task type="auto">
187
- <name>create-deprecation-registry-tests</name>
188
- <files>
189
- test/lib/source_monitor/configuration/deprecation_registry_test.rb
190
- </files>
191
- <action>
192
- Create `test/lib/source_monitor/configuration/deprecation_registry_test.rb` with comprehensive tests for the deprecation framework.
193
-
194
- Module nesting: `SourceMonitor::Configuration::DeprecationRegistryTest < ActiveSupport::TestCase`.
195
-
196
- Setup/teardown:
197
- ```ruby
198
- setup do
199
- SourceMonitor.reset_configuration!
200
- DeprecationRegistry.clear!
201
- end
202
-
203
- teardown do
204
- DeprecationRegistry.clear!
205
- SourceMonitor.reset_configuration!
206
- end
207
- ```
208
-
209
- Tests to write (10 tests covering all branches):
210
-
211
- 1. **"register stores entry in registry"** -- Register a synthetic deprecation `"http.old_proxy_url"`. Assert `DeprecationRegistry.registered?("http.old_proxy_url")` is true. Assert `DeprecationRegistry.entries` has one entry with correct attributes.
212
-
213
- 2. **"warning severity logs deprecation and forwards to replacement"** -- Register `"http.old_proxy_url"` with severity: :warning, replacement: "http.proxy", removed_in: "0.5.0". Use a mock or string IO to capture Rails.logger.warn output. Call `SourceMonitor.configure { |c| c.http.old_proxy_url = "socks5://localhost" }`. Assert the warning message was logged (contains "DEPRECATION", "old_proxy_url", "0.5.0", "http.proxy"). Assert `SourceMonitor.config.http.proxy` equals "socks5://localhost" (forwarded).
214
-
215
- 3. **"warning severity reader forwards to replacement getter"** -- After registering and setting via the deprecated writer (as in test 2), read `config.http.old_proxy_url` and assert it returns the same value as `config.http.proxy`.
216
-
217
- 4. **"error severity raises DeprecatedOptionError on write"** -- Register `"http.removed_option"` with severity: :error, removed_in: "0.5.0", message: "This option was removed. Use X instead." Assert that `SourceMonitor.configure { |c| c.http.removed_option = "value" }` raises `SourceMonitor::DeprecatedOptionError` with message containing "removed_option" and "0.5.0".
218
-
219
- 5. **"error severity raises DeprecatedOptionError on read"** -- Register same as test 4. Assert that calling `SourceMonitor.config.http.removed_option` raises `SourceMonitor::DeprecatedOptionError`.
220
-
221
- 6. **"clear removes defined methods and entries"** -- Register a deprecation. Assert registered. Call `clear!`. Assert NOT registered. Assert `entries` is empty. Assert `SourceMonitor.config.http` does NOT respond_to the deprecated method name.
222
-
223
- 7. **"top-level option deprecation works"** -- Register `"old_queue_prefix"` (no dot -- targets Configuration directly) with severity: :warning, replacement: "queue_namespace". Configure with `config.old_queue_prefix = "my_app"`. Assert warning logged. Assert `config.queue_namespace` equals "my_app".
224
-
225
- 8. **"no warnings for valid current configuration"** (zero false positives criterion) -- Register a deprecation for a synthetic option. Then configure using ONLY valid current options (e.g. `config.http.timeout = 30`). Assert NO deprecation warnings were logged.
226
-
227
- 9. **"multiple deprecations can coexist"** -- Register two deprecations on different settings classes. Trigger both in one configure block. Assert both warnings logged.
228
-
229
- 10. **"check_deprecations! is called during configure"** -- Use a mock to verify that `check_deprecations!` is called. This validates the wiring from task 2.
230
-
231
- For capturing Rails.logger.warn output, use:
232
- ```ruby
233
- log_output = StringIO.new
234
- original_logger = Rails.logger
235
- Rails.logger = ActiveSupport::Logger.new(log_output)
236
- # ... run configure ...
237
- Rails.logger = original_logger
238
- assert_match(/DEPRECATION/, log_output.string)
239
- ```
240
-
241
- All 10 tests should pass. RuboCop clean.
242
- </action>
243
- <verify>
244
- Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/configuration/deprecation_registry_test.rb` -- all 10 tests pass with 0 failures. Run `bin/rubocop test/lib/source_monitor/configuration/deprecation_registry_test.rb` -- 0 offenses.
245
- </verify>
246
- <done>
247
- 10 tests pass covering: registration, warning with forwarding, warning reader, error on write, error on read, clear cleanup, top-level option, zero false positives, multiple coexistence, and configure wiring. RuboCop clean.
248
- </done>
249
- </task>
250
- <task type="auto">
251
- <name>full-suite-verification-and-brakeman</name>
252
- <files>
253
- </files>
254
- <action>
255
- Run the full verification suite to confirm no regressions and all quality gates pass.
256
-
257
- 1. `bin/rails test` -- full test suite passes with 992+ runs, 0 failures (the 10 new deprecation tests + existing 992)
258
- 2. `bin/rubocop` -- 0 offenses across all files
259
- 3. `bin/brakeman --no-pager` -- 0 warnings
260
-
261
- If any failures:
262
- - Test failures: read the failure output, identify the root cause, fix in the appropriate file
263
- - RuboCop offenses: fix style issues in the offending files
264
- - Brakeman warnings: evaluate and fix security concerns
265
-
266
- After all gates pass, confirm:
267
- - `grep -rn 'class DeprecationRegistry' lib/` returns the registry file
268
- - `grep -rn 'check_deprecations!' lib/source_monitor/configuration.rb` returns the method
269
- - `grep -rn 'check_deprecations!' lib/source_monitor.rb` returns the configure hook
270
- - `grep -rn 'DeprecatedOptionError' lib/` returns the error class definition
271
- - The existing configuration_test.rb still passes (reset_configuration! does not interfere with registry)
272
- </action>
273
- <verify>
274
- `bin/rails test` exits 0 with 1002+ runs, 0 failures. `bin/rubocop` exits 0. `bin/brakeman --no-pager` exits 0. All grep checks return matches.
275
- </verify>
276
- <done>
277
- Full suite green with 1002+ runs. RuboCop clean. Brakeman clean. All Phase 2 success criteria met. REQ-28 implemented: deprecation registry with DSL, boot-time warnings for :warning severity, errors for :error severity, zero false positives on current valid config.
278
- </done>
279
- </task>
280
- </tasks>
281
- <verification>
282
- 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/configuration/deprecation_registry_test.rb` -- 10 tests pass
283
- 2. `bin/rails test` -- 1002+ runs, 0 failures
284
- 3. `bin/rubocop` -- 0 offenses
285
- 4. `bin/brakeman --no-pager` -- 0 warnings
286
- 5. `grep -n 'class DeprecationRegistry' lib/source_monitor/configuration/deprecation_registry.rb` returns a match
287
- 6. `grep -n 'class DeprecatedOptionError' lib/source_monitor/configuration/deprecation_registry.rb` returns a match
288
- 7. `grep -n 'check_deprecations!' lib/source_monitor/configuration.rb` returns a match
289
- 8. `grep -n 'check_deprecations!' lib/source_monitor.rb` returns a match
290
- 9. `grep -n 'DeprecationRegistry' lib/source_monitor/configuration.rb` returns a match (require)
291
- 10. Existing `test/lib/source_monitor/configuration_test.rb` passes without modification
292
- </verification>
293
- <success_criteria>
294
- - Engine maintains a deprecation registry with option path, version deprecated, replacement, and severity (REQ-28)
295
- - At configuration load time, deprecated option usage triggers Rails.logger.warn with actionable message (REQ-28)
296
- - Removed options that are still referenced raise DeprecatedOptionError with migration path (REQ-28)
297
- - Framework is opt-in via simple DSL: DeprecationRegistry.register(path, removed_in:, ...) (REQ-28)
298
- - Zero false positives on current valid configuration -- only synthetic test deprecations trigger warnings (REQ-28)
299
- - bin/rails test passes with 1002+ runs, RuboCop clean, Brakeman clean
300
- </success_criteria>
301
- <output>
302
- .vbw-planning/phases/02-config-deprecation/PLAN-01-SUMMARY.md
303
- </output>