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,345 +0,0 @@
1
- ---
2
- phase: 6
3
- plan: "01"
4
- title: ssl-cert-store-configuration
5
- type: execute
6
- wave: 1
7
- depends_on: []
8
- cross_phase_deps: []
9
- autonomous: true
10
- effort_override: thorough
11
- skills_used: []
12
- files_modified:
13
- - lib/source_monitor/configuration/http_settings.rb
14
- - lib/source_monitor/http.rb
15
- - test/lib/source_monitor/http_test.rb
16
- - test/lib/source_monitor/fetching/feed_fetcher_test.rb
17
- - test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml
18
- must_haves:
19
- truths:
20
- - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/http_test.rb` exits 0 with 0 failures"
21
- - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0 with 0 failures"
22
- - "Running `bin/rubocop lib/source_monitor/http.rb lib/source_monitor/configuration/http_settings.rb test/lib/source_monitor/http_test.rb test/lib/source_monitor/fetching/feed_fetcher_test.rb` exits 0 with no offenses"
23
- - "Running `bin/rails test` (full suite) exits 0 with 0 failures"
24
- - "`grep -r 'cert_store' lib/source_monitor/http.rb` returns at least one match"
25
- - "`grep -r 'ssl_ca_file' lib/source_monitor/configuration/http_settings.rb` returns at least one match"
26
- - "The VCR cassette at test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml exists and contains 'netflixtechblog'"
27
- artifacts:
28
- - path: "lib/source_monitor/http.rb"
29
- provides: "SSL cert store configuration on Faraday connections"
30
- contains: "cert_store"
31
- - path: "lib/source_monitor/configuration/http_settings.rb"
32
- provides: "Configurable SSL options (ca_file, ca_path, verify)"
33
- contains: "ssl_ca_file"
34
- - path: "test/lib/source_monitor/http_test.rb"
35
- provides: "Tests for SSL configuration on HTTP client"
36
- contains: "ssl"
37
- - path: "test/lib/source_monitor/fetching/feed_fetcher_test.rb"
38
- provides: "Netflix Tech Blog VCR regression test"
39
- contains: "netflix"
40
- - path: "test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml"
41
- provides: "Recorded VCR cassette from real Netflix Tech Blog feed"
42
- contains: "netflixtechblog"
43
- key_links:
44
- - from: "http.rb#configure_ssl"
45
- to: "REQ-25"
46
- via: "Configures Faraday SSL with system cert store to fix certificate verification failures"
47
- - from: "http_settings.rb#ssl_ca_file"
48
- to: "REQ-25"
49
- via: "Exposes configurable SSL CA file/path for environments with non-standard cert locations"
50
- - from: "feed_fetcher_test.rb#netflix_regression"
51
- to: "REQ-25"
52
- via: "VCR cassette proves Netflix Tech Blog feed parses successfully"
53
- ---
54
- <objective>
55
- Fix SSL certificate verification failures (like Netflix Tech Blog's "unable to get local issuer certificate") by configuring the Faraday HTTP client to use a properly initialized OpenSSL cert store with system default paths. Add configurable SSL options to HTTPSettings so users can override CA file/path in non-standard environments. Record a VCR cassette from the real Netflix Tech Blog feed as a regression test. REQ-25.
56
- </objective>
57
- <context>
58
- @lib/source_monitor/http.rb -- The HTTP client module. Creates Faraday connections via `HTTP.client`. Currently configures timeouts, retry, gzip, follow-redirects, and headers -- but does NOT configure any SSL options. Faraday's `connection.ssl` is left at defaults, which means it relies on the underlying adapter (net_http) to find CA certs. On some systems (macOS with Homebrew Ruby, Docker Alpine, custom OpenSSL builds), the compiled-in `OpenSSL::X509::DEFAULT_CERT_FILE` path may not include all intermediate certificates -- causing "certificate verify failed" for sites like Netflix Tech Blog whose chain depends on intermediates served by the TLS handshake being validated against a complete CA bundle. The fix is to explicitly set `connection.ssl.cert_store` to an `OpenSSL::X509::Store` initialized with `set_default_paths`, which loads both `DEFAULT_CERT_FILE` and `DEFAULT_CERT_DIR`. Optionally, if the user configures `ssl_ca_file` or `ssl_ca_path`, those override the default store.
59
-
60
- @lib/source_monitor/configuration/http_settings.rb -- The settings class for HTTP configuration. Has 11 `attr_accessor` fields for timeout, retry, proxy, headers, etc. New SSL settings (`ssl_ca_file`, `ssl_ca_path`, `ssl_verify`) should be added here following the same pattern. Default: `ssl_verify = true` (never disable verification), `ssl_ca_file = nil`, `ssl_ca_path = nil` (nil means use system defaults via cert_store).
61
-
62
- @test/lib/source_monitor/http_test.rb -- 8 existing tests for the HTTP client. Tests inspect `@connection.builder.handlers`, `@connection.options`, and `@connection.headers`. New SSL tests should inspect `@connection.ssl.cert_store`, `@connection.ssl.verify`, and optionally `@connection.ssl.ca_file` when configured.
63
-
64
- @test/lib/source_monitor/fetching/feed_fetcher_test.rb -- Existing tests use VCR cassettes for RSS, Atom, and JSON feeds (ruby-lang.org, W3C, json_sample). The Netflix regression test should follow the same pattern: `VCR.use_cassette("source_monitor/fetching/netflix_medium_rss")` with a source pointing at `https://netflixtechblog.com/feed`.
65
-
66
- @test/vcr_cassettes/ -- Contains 3 existing cassettes (rss_success, atom_success, json_success). The Netflix cassette should be recorded with `VCR.use_cassette(..., record: :new_episodes)` during the first test run against the real feed (with WebMock allowing the Netflix host temporarily), then committed as a fixture for CI. This requires temporarily allowing net connect to netflixtechblog.com during recording.
67
-
68
- @lib/source_monitor/fetching/feed_fetcher.rb -- Lines 84-85 already catch `Faraday::SSLError` and wrap it as `ConnectionError`. This error path will stop triggering once SSL is properly configured, but the error handling remains as a safety net for genuinely invalid certificates.
69
-
70
- **Root cause analysis:**
71
- The Netflix Tech Blog (Medium-hosted at netflixtechblog.com, IP 52.1.173.203) serves a TLS certificate chain that requires the client to have Amazon's intermediate CA in its trust store. Ruby's compiled-in `OpenSSL::X509::DEFAULT_CERT_FILE` may point to a cert bundle that is missing this intermediate, or the system's cert directory may not be indexed. By explicitly creating an `OpenSSL::X509::Store` with `set_default_paths` and assigning it to the Faraday connection's `ssl.cert_store`, we ensure Ruby loads all available system certificates -- which on a properly maintained system includes Amazon/AWS intermediates. This is the standard, general fix for SSL verification issues in Ruby HTTP clients.
72
-
73
- **Key design decisions:**
74
- 1. Use `OpenSSL::X509::Store.new.tap(&:set_default_paths)` as the default cert store -- this is the most cross-platform approach
75
- 2. Add `ssl_ca_file` and `ssl_ca_path` as optional overrides in HTTPSettings -- when set, they configure `connection.ssl.ca_file` / `connection.ssl.ca_path` instead of using the cert store
76
- 3. Keep `ssl_verify = true` as default and do NOT add a way to disable verification globally -- security-first design
77
- 4. The cert store is created fresh per `HTTP.client` call (Faraday connections are short-lived and not shared across threads)
78
- 5. For recording the VCR cassette: use a dedicated recording script or a test with `record: :new_episodes` and temporarily permit net connect
79
- </context>
80
- <tasks>
81
- <task type="auto">
82
- <name>add-ssl-settings-to-http-settings</name>
83
- <files>
84
- lib/source_monitor/configuration/http_settings.rb
85
- </files>
86
- <action>
87
- Add three new `attr_accessor` fields to `HTTPSettings` for SSL configuration:
88
-
89
- 1. `ssl_ca_file` -- Path to a CA certificate file (PEM format). When set, Faraday uses this instead of the default cert store. Default: `nil`.
90
- 2. `ssl_ca_path` -- Path to a directory of CA certificates. When set, Faraday uses this. Default: `nil`.
91
- 3. `ssl_verify` -- Whether to verify SSL certificates. Default: `true`. This exists for completeness but should almost never be set to `false`.
92
-
93
- Add the three new fields to the `attr_accessor` list (after `retry_statuses`):
94
-
95
- ```ruby
96
- attr_accessor :timeout,
97
- :open_timeout,
98
- :max_redirects,
99
- :user_agent,
100
- :proxy,
101
- :headers,
102
- :retry_max,
103
- :retry_interval,
104
- :retry_interval_randomness,
105
- :retry_backoff_factor,
106
- :retry_statuses,
107
- :ssl_ca_file,
108
- :ssl_ca_path,
109
- :ssl_verify
110
- ```
111
-
112
- In `reset!`, add after `@retry_statuses = nil`:
113
-
114
- ```ruby
115
- @ssl_ca_file = nil
116
- @ssl_ca_path = nil
117
- @ssl_verify = true
118
- ```
119
- </action>
120
- <verify>
121
- Read `lib/source_monitor/configuration/http_settings.rb` and confirm: (a) all three new attr_accessors are present, (b) `reset!` initializes them with correct defaults, (c) `ssl_verify` defaults to `true`.
122
- </verify>
123
- <done>
124
- HTTPSettings now has ssl_ca_file, ssl_ca_path, and ssl_verify configuration options with safe defaults.
125
- </done>
126
- </task>
127
- <task type="auto">
128
- <name>configure-faraday-ssl-cert-store</name>
129
- <files>
130
- lib/source_monitor/http.rb
131
- </files>
132
- <action>
133
- Modify the `HTTP` module to configure SSL on every Faraday connection. Add a `require "openssl"` at the top of the file (after the existing requires).
134
-
135
- In the `configure_request` method, add SSL configuration BEFORE the adapter line (`connection.adapter Faraday.default_adapter`):
136
-
137
- ```ruby
138
- configure_ssl(connection, settings)
139
- ```
140
-
141
- Add a new private method `configure_ssl`:
142
-
143
- ```ruby
144
- def configure_ssl(connection, settings)
145
- connection.ssl.verify = settings.ssl_verify != false
146
-
147
- if settings.ssl_ca_file
148
- connection.ssl.ca_file = settings.ssl_ca_file
149
- elsif settings.ssl_ca_path
150
- connection.ssl.ca_path = settings.ssl_ca_path
151
- else
152
- connection.ssl.cert_store = default_cert_store
153
- end
154
- end
155
-
156
- def default_cert_store
157
- OpenSSL::X509::Store.new.tap(&:set_default_paths)
158
- end
159
- ```
160
-
161
- The logic:
162
- 1. Always set `verify = true` unless explicitly configured to `false` (defense in depth).
163
- 2. If user specifies `ssl_ca_file`, use that (overrides cert store).
164
- 3. Else if user specifies `ssl_ca_path`, use that (overrides cert store).
165
- 4. Otherwise, create a fresh `OpenSSL::X509::Store` with `set_default_paths` -- this is the key fix that resolves the Netflix SSL error by loading all system CA certificates including intermediates.
166
-
167
- Note: `ca_file` and `ca_path` take precedence over `cert_store` in Faraday/net_http, so we only set one path.
168
- </action>
169
- <verify>
170
- Read `lib/source_monitor/http.rb` and confirm: (a) `require "openssl"` is present, (b) `configure_ssl` is called in `configure_request`, (c) the method creates an `OpenSSL::X509::Store` with `set_default_paths` as the default, (d) `ssl_ca_file` and `ssl_ca_path` override the store when set, (e) `ssl.verify` is always explicitly set. Run `bin/rubocop lib/source_monitor/http.rb` to confirm no offenses.
171
- </verify>
172
- <done>
173
- The HTTP client now explicitly configures SSL with a proper cert store. By default, every Faraday connection gets an OpenSSL::X509::Store initialized with system default paths, which resolves certificate chain verification failures like the Netflix Tech Blog issue.
174
- </done>
175
- </task>
176
- <task type="auto">
177
- <name>add-ssl-unit-tests</name>
178
- <files>
179
- test/lib/source_monitor/http_test.rb
180
- </files>
181
- <action>
182
- Add the following tests to `HTTPTest`:
183
-
184
- 1. **"configures SSL with default cert store"** -- Create a default client, assert `@connection.ssl.verify` is truthy, assert `@connection.ssl.cert_store` is an instance of `OpenSSL::X509::Store`, assert `@connection.ssl.ca_file` is nil (not overridden).
185
-
186
- 2. **"uses configured ssl_ca_file when set"** -- Configure `config.http.ssl_ca_file = "/path/to/custom/ca.pem"`, create a client, assert `connection.ssl.ca_file` equals the configured path, assert `connection.ssl.cert_store` is nil (ca_file takes precedence).
187
-
188
- 3. **"uses configured ssl_ca_path when set"** -- Configure `config.http.ssl_ca_path = "/path/to/certs"`, create a client, assert `connection.ssl.ca_path` equals the configured path.
189
-
190
- 4. **"ssl verify defaults to true"** -- Create a default client, assert `connection.ssl.verify` is `true`.
191
-
192
- 5. **"respects ssl_verify configuration"** -- Configure `config.http.ssl_verify = false`, create a client, assert `connection.ssl.verify` is `false`. (This tests the escape hatch exists, even though it should rarely be used.)
193
-
194
- Add `require "openssl"` at the top of the test file if not already present.
195
-
196
- Each test should follow the existing pattern: create a connection via `SourceMonitor::HTTP.client`, then inspect the `connection.ssl` object.
197
- </action>
198
- <verify>
199
- Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/http_test.rb` and confirm all tests pass (8 existing + 5 new = 13 tests). Run `bin/rubocop test/lib/source_monitor/http_test.rb` and confirm no offenses.
200
- </verify>
201
- <done>
202
- 5 new SSL configuration tests added. All 13 HTTP client tests pass. The cert store, ca_file, ca_path, and verify options are all verified.
203
- </done>
204
- </task>
205
- <task type="auto">
206
- <name>record-netflix-vcr-cassette-and-regression-test</name>
207
- <files>
208
- test/lib/source_monitor/fetching/feed_fetcher_test.rb
209
- test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml
210
- </files>
211
- <action>
212
- This task records a VCR cassette from the real Netflix Tech Blog feed and adds a regression test.
213
-
214
- **Step 1: Record the VCR cassette.**
215
-
216
- Create a temporary recording script or use a one-off test run. The simplest approach: add the test first (below), then run it once with `VCR_RECORD=new_episodes` or equivalent to record the cassette. The cassette will be committed as a test fixture.
217
-
218
- To record, temporarily allow net connect for the Netflix host. You can do this by running:
219
-
220
- ```bash
221
- PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb -n test_fetches_netflix_tech_blog_feed_via_medium_rss
222
- ```
223
-
224
- with VCR configured to `record: :new_episodes` for this specific cassette. If WebMock blocks the request, temporarily use `WebMock.allow_net_connect!` inside the test during recording, then remove it after the cassette is committed.
225
-
226
- **Alternative recording approach:** Use a standalone Ruby script to fetch the feed and manually create the VCR cassette YAML:
227
-
228
- ```ruby
229
- require "faraday"
230
- require "openssl"
231
- require "yaml"
232
-
233
- conn = Faraday.new do |f|
234
- f.ssl.cert_store = OpenSSL::X509::Store.new.tap(&:set_default_paths)
235
- f.request :gzip
236
- f.response :follow_redirects, limit: 5
237
- f.adapter :net_http
238
- end
239
-
240
- response = conn.get("https://netflixtechblog.com/feed")
241
- # Save as VCR cassette format...
242
- ```
243
-
244
- After recording, verify the cassette file exists at `test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml` and contains a 200 response with RSS/XML body containing Netflix blog entries.
245
-
246
- **Step 2: Add the regression test.**
247
-
248
- Add a new test to `FeedFetcherTest`:
249
-
250
- ```ruby
251
- test "fetches Netflix Tech Blog feed via Medium RSS" do
252
- source = build_source(
253
- name: "Netflix Tech Blog",
254
- feed_url: "https://netflixtechblog.com/feed"
255
- )
256
-
257
- result = nil
258
- VCR.use_cassette("source_monitor/fetching/netflix_medium_rss") do
259
- result = FeedFetcher.new(source: source, jitter: ->(_) { 0 }).call
260
- end
261
-
262
- assert_equal :fetched, result.status
263
- assert_not_nil result.feed
264
- assert_kind_of Feedjira::Parser::RSS, result.feed
265
- assert result.feed.entries.any?, "Expected at least one feed entry"
266
- assert_match(/netflix/i, result.feed.title.to_s)
267
- end
268
- ```
269
-
270
- This test uses the recorded VCR cassette so it works in CI without network access. It validates that the feed parses as RSS and contains Netflix entries.
271
-
272
- **Important:** The `build_source` helper is already available in this test file. Check existing test patterns to confirm the helper signature.
273
- </action>
274
- <verify>
275
- Confirm: (a) the VCR cassette file exists at `test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml`, (b) it contains `netflixtechblog` in the request URI, (c) the response status is 200, (d) the response body contains RSS/XML content. Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb -n test_fetches_Netflix_Tech_Blog_feed_via_Medium_RSS` and confirm it passes.
276
- </verify>
277
- <done>
278
- VCR cassette recorded from real Netflix Tech Blog feed. Regression test passes using the cassette. The feed parses as RSS with Netflix blog entries, proving the SSL fix resolves the original "certificate verify failed" error.
279
- </done>
280
- </task>
281
- <task type="auto">
282
- <name>full-suite-verification-and-documentation</name>
283
- <files>
284
- lib/source_monitor/http.rb
285
- lib/source_monitor/configuration/http_settings.rb
286
- test/lib/source_monitor/http_test.rb
287
- test/lib/source_monitor/fetching/feed_fetcher_test.rb
288
- </files>
289
- <action>
290
- Run the full verification suite:
291
-
292
- 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/http_test.rb test/lib/source_monitor/fetching/feed_fetcher_test.rb` -- all targeted tests pass
293
- 2. `bin/rails test` -- full suite passes with 874+ runs and 0 failures
294
- 3. `bin/rubocop` -- zero offenses
295
- 4. `bin/brakeman --no-pager` -- zero warnings
296
-
297
- Review all modified files for:
298
- - `http.rb`: `require "openssl"` present, `configure_ssl` called in `configure_request`, `default_cert_store` creates `OpenSSL::X509::Store` with `set_default_paths`
299
- - `http_settings.rb`: three new attr_accessors (`ssl_ca_file`, `ssl_ca_path`, `ssl_verify`), initialized in `reset!`
300
- - `http_test.rb`: 5 new SSL tests covering cert_store default, ca_file override, ca_path override, verify default, verify override
301
- - `feed_fetcher_test.rb`: Netflix regression test using VCR cassette
302
- - VCR cassette: valid YAML with Netflix feed content
303
-
304
- If any failures or offenses are found, fix them before completing.
305
-
306
- Add a brief inline comment in `http.rb` above `configure_ssl` documenting the root cause:
307
-
308
- ```ruby
309
- # Configure SSL to use a proper cert store. Without this, some systems
310
- # fail to verify certificate chains that depend on intermediate CAs
311
- # (e.g., Medium/Netflix on AWS). OpenSSL::X509::Store#set_default_paths
312
- # loads all system-trusted CAs including intermediates.
313
- ```
314
- </action>
315
- <verify>
316
- `bin/rails test` exits 0 with 874+ runs, 0 failures. `bin/rubocop` exits 0 with 0 offenses. `bin/brakeman --no-pager` exits 0 with 0 warnings. All modified files are clean and well-documented.
317
- </verify>
318
- <done>
319
- Full suite passes. All quality gates green. SSL cert store fix is general (not Netflix-specific), configurable via HTTPSettings, documented inline, and regression-tested with a VCR cassette.
320
- </done>
321
- </task>
322
- </tasks>
323
- <verification>
324
- 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/http_test.rb` -- 13+ tests pass (8 existing + 5 new SSL tests)
325
- 2. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/fetching/feed_fetcher_test.rb` -- all tests pass including Netflix regression
326
- 3. `bin/rails test` -- 874+ runs, 0 failures
327
- 4. `bin/rubocop` -- 0 offenses
328
- 5. `bin/brakeman --no-pager` -- 0 warnings
329
- 6. `grep -n 'cert_store' lib/source_monitor/http.rb` returns matches for configure_ssl and default_cert_store
330
- 7. `grep -n 'ssl_ca_file' lib/source_monitor/configuration/http_settings.rb` returns match in attr_accessor and reset!
331
- 8. `test -f test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml` exits 0
332
- 9. `grep 'netflixtechblog' test/vcr_cassettes/source_monitor/fetching/netflix_medium_rss.yml` returns matches
333
- </verification>
334
- <success_criteria>
335
- - Root cause identified: missing intermediate CA certs when OpenSSL cert store not explicitly initialized (REQ-25)
336
- - General fix applied: Faraday SSL configured with OpenSSL::X509::Store#set_default_paths on every connection (REQ-25)
337
- - Configurable: ssl_ca_file, ssl_ca_path, ssl_verify exposed via HTTPSettings for non-standard environments (REQ-25)
338
- - Netflix Tech Blog feed fetches successfully via VCR cassette regression test (REQ-25)
339
- - No regressions: existing SSL error wrapping (Faraday::SSLError -> ConnectionError) still works (REQ-25)
340
- - VCR cassette recorded from real Netflix feed and committed as test fixture (REQ-25)
341
- - All tests pass, RuboCop clean, Brakeman clean (REQ-25)
342
- </success_criteria>
343
- <output>
344
- .vbw-planning/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md
345
- </output>
@@ -1,80 +0,0 @@
1
- <!-- VBW REQUIREMENTS TEMPLATE (ARTF-06) -- Structured requirements with traceability -->
2
- <!-- Created by Architect agent during /vbw scope -->
3
-
4
- # SourceMonitor Requirements
5
-
6
- Defined: 2026-02-09
7
- Core value: Drop-in Rails engine for feed monitoring, content scraping, and operational dashboards.
8
-
9
- ## v1 Requirements
10
-
11
- ### Test Coverage
12
-
13
- - [ ] **REQ-01**: Close coverage gaps in `FeedFetcher` -- add tests for uncovered branches in the fetch pipeline
14
- - [ ] **REQ-02**: Close coverage gaps in `ItemCreator` -- add tests for item creation edge cases
15
- - [ ] **REQ-03**: Close coverage gaps in `Configuration` -- test nested settings classes and edge cases
16
- - [ ] **REQ-04**: Close coverage gaps in `Dashboard::Queries` -- test dashboard query logic
17
- - [ ] **REQ-05**: Close coverage gaps in `Broadcaster` -- test realtime broadcasting logic
18
- - [ ] **REQ-06**: Close coverage gaps in `BulkSourceScraper` -- test bulk scraping workflows
19
- - [ ] **REQ-07**: Close coverage gaps in `SourcesIndexMetrics` -- test analytics calculations
20
-
21
- ### Refactoring
22
-
23
- - [ ] **REQ-08**: Extract `FeedFetcher` (627 lines) into focused single-responsibility classes
24
- - [ ] **REQ-09**: Extract `Configuration` (655 lines) nested settings classes into separate files
25
- - [ ] **REQ-10**: Extract `ImportSessionsController` (792 lines) wizard steps into step-specific concerns or service objects
26
- - [ ] **REQ-11**: Fix `LogEntry` hard-coded table name to use configurable prefix system
27
- - [ ] **REQ-12**: Replace eager 102+ require statements in `lib/source_monitor.rb` with autoloading
28
-
29
- ### Code Quality
30
-
31
- - [ ] **REQ-13**: Ensure frozen_string_literal is consistent across all Ruby files
32
- - [ ] **REQ-14**: Audit and fix any RuboCop violations against omakase ruleset
33
- - [ ] **REQ-15**: Ensure all models, controllers, and service objects follow Rails conventions
34
-
35
- ### Generator Enhancements
36
-
37
- - [ ] **REQ-16**: Install generator patches `Procfile.dev` with a `jobs:` entry for Solid Queue
38
- - [ ] **REQ-17**: Install generator patches queue config dispatcher with `recurring_schedule: config/recurring.yml`
39
- - [ ] **REQ-18**: Guided workflow (`Setup::Workflow`) integrates both new generator steps
40
- - [ ] **REQ-19**: `RecurringScheduleVerifier` checks that recurring tasks are registered with Solid Queue
41
- - [ ] **REQ-20**: `SolidQueueVerifier` remediation suggests `Procfile.dev` when workers not detected
42
- - [ ] **REQ-21**: Skills and documentation updated to reflect automated Procfile.dev and recurring_schedule setup
43
-
44
- ### Dashboard UX
45
-
46
- - [ ] **REQ-22**: Fetch logs show source URL for both success and failure entries on the dashboard
47
- - [ ] **REQ-23**: Dashboard links to sources and items are clickable and open in a new tab
48
-
49
- ### Active Storage Image Downloads
50
-
51
- - [ ] **REQ-24**: Configurable option to download inline images from items to Active Storage instead of loading from source
52
-
53
- ### Feed Compatibility
54
-
55
- - [ ] **REQ-25**: Investigate and fix failing fetch for Netflix Tech Blog feed (https://netflixtechblog.com/feed)
56
-
57
- ### Upgrade Assurance
58
-
59
- - [ ] **REQ-26**: `bin/source_monitor upgrade` command detects version changes, copies new migrations, re-runs generator, and runs verification
60
- - [ ] **REQ-27**: `PendingMigrationsVerifier` checks for unmigrated SourceMonitor tables in the verification suite
61
- - [ ] **REQ-28**: Configuration deprecation framework warns on stale, renamed, or removed initializer options at boot time
62
- - [ ] **REQ-29**: `sm-upgrade` AI skill teaches agents how to handle gem updates with CHANGELOG parsing and step-by-step guidance
63
- - [ ] **REQ-30**: Upgrade guide documentation (`docs/upgrade.md`) with version-specific instructions
64
-
65
- ## v2 Requirements
66
-
67
- - [ ] **REQ-XX**: Improve optional dependency loading with clear error messages
68
- - [ ] **REQ-XX**: Add database index verification tooling
69
- - [ ] **REQ-XX**: Document health check endpoint response format
70
-
71
- ## Out of Scope
72
-
73
- | Item | Reason |
74
- |------|--------|
75
- | Multi-database support (MySQL/SQLite) | PostgreSQL-only simplifies development |
76
- | Built-in authentication | Host app responsibility |
77
-
78
- ## Traceability
79
-
80
- Requirement-to-phase mapping is tracked in ROADMAP.md.
@@ -1,75 +0,0 @@
1
- <!-- VBW ROADMAP -- Phase decomposition with requirement mapping -->
2
- <!-- Created during /vbw scope -->
3
-
4
- # SourceMonitor Upgrade Assurance Roadmap
5
-
6
- **Milestone:** upgrade-assurance
7
- **Goal:** Give host app developers confidence that gem updates go smoothly -- automated migration detection, upgrade command, config validation, and AI-assisted upgrade guidance.
8
-
9
- ## Phases
10
-
11
- 1. [x] Phase 1: Upgrade Command & Migration Verifier
12
- 2. [x] Phase 2: Configuration Deprecation Framework
13
- 3. [x] Phase 3: Upgrade Skill & Documentation
14
-
15
- ## Phase Details
16
-
17
- ### Phase 1: Upgrade Command & Migration Verifier
18
-
19
- **Goal:** Add `bin/source_monitor upgrade` that detects version changes since last install, copies new migrations, re-runs the idempotent generator, runs verification, and reports what changed. Also add a `PendingMigrationsVerifier` to the existing verification suite.
20
-
21
- **Requirements:** REQ-26, REQ-27
22
-
23
- **Success Criteria:**
24
- - `bin/source_monitor upgrade` compares stored version marker against `SourceMonitor::VERSION`
25
- - If version changed: copies new migrations, re-runs generator, runs `bin/source_monitor verify`
26
- - If no version change: reports "Already up to date" with current version
27
- - `PendingMigrationsVerifier` checks `db:migrate:status` for unmigrated SourceMonitor migrations
28
- - Verifier integrated into `bin/source_monitor verify` and the upgrade flow
29
- - Version marker stored in host app (e.g., `.source_monitor_version` or DB-backed)
30
- - `bin/rails test` passes, RuboCop clean
31
-
32
- ### Phase 2: Configuration Deprecation Framework
33
-
34
- **Goal:** Add a lightweight framework that warns host app developers when their initializer uses config options that have been renamed, removed, or have changed defaults. Warnings appear at boot time via Rails logger.
35
-
36
- **Requirements:** REQ-28
37
-
38
- **Success Criteria:**
39
- - Engine maintains a deprecation registry (option name, version deprecated, replacement if any)
40
- - At configuration load time, deprecated option usage triggers a Rails.logger.warn with actionable message
41
- - Removed options that are still referenced raise a clear error with migration path
42
- - Framework is opt-in for engine developers (simple DSL to register deprecations)
43
- - Zero false positives on current valid configuration
44
- - `bin/rails test` passes, RuboCop clean
45
-
46
- ### Phase 3: Upgrade Skill & Documentation
47
-
48
- **Goal:** Create an `sm-upgrade` AI skill that guides agents through post-update workflows, and write a versioned upgrade guide for human developers.
49
-
50
- **Requirements:** REQ-29, REQ-30
51
-
52
- **Success Criteria:**
53
- - `sm-upgrade` skill covers: reading CHANGELOG between versions, running upgrade command, interpreting results, handling edge cases
54
- - Skill references the upgrade command and verification suite
55
- - `docs/upgrade.md` includes: general upgrade steps, version-specific notes (0.3.x → 0.4.x), troubleshooting
56
- - Skills installer updated to include `sm-upgrade` in consumer set
57
- - Existing `sm-host-setup` skill cross-references upgrade flow
58
-
59
- ## Progress
60
-
61
- | Phase | Status | Plans |
62
- |-------|--------|-------|
63
- | 1 | Complete | PLAN-01 (5 tasks, 5 commits) |
64
- | 2 | Complete | PLAN-01 (4 tasks, 3 commits) |
65
- | 3 | Complete | PLAN-01 (5 tasks, 4 commits) |
66
-
67
- ## Requirement Mapping
68
-
69
- | REQ | Phase | Description |
70
- |-----|-------|-------------|
71
- | REQ-26 | 1 | Upgrade command with version detection and auto-remediation |
72
- | REQ-27 | 1 | PendingMigrationsVerifier in verification suite |
73
- | REQ-28 | 2 | Config deprecation framework with boot-time warnings |
74
- | REQ-29 | 3 | sm-upgrade AI skill for agent-guided updates |
75
- | REQ-30 | 3 | Upgrade guide documentation |
@@ -1,29 +0,0 @@
1
- <!-- VBW STATE -- Current milestone progress -->
2
-
3
- # State
4
-
5
- **Milestone:** upgrade-assurance
6
- **Current Phase:** All phases complete
7
- **Status:** Ready to archive
8
- **Date:** 2026-02-13
9
-
10
- ## Progress
11
-
12
- - Phase 1: Complete (1 plan, 5 tasks, 5 commits) -- QA PASS 34/34
13
- - Phase 2: Complete (1 plan, 4 tasks, 3 commits)
14
- - Phase 3: Complete (1 plan, 5 tasks, 4 commits) -- QA PASS 37/38
15
-
16
- ## Decisions
17
-
18
- | Decision | Date | Rationale |
19
- |----------|------|-----------|
20
- | 3 phases: command, config, skill | 2026-02-12 | Each independently valuable; command is foundational |
21
-
22
- ## Metrics
23
-
24
- | Metric | Value |
25
- |--------|-------|
26
- | Phases | 3 |
27
- | Plans completed | 3 |
28
- | Tasks completed | 14 |
29
- | Tests | 1003 |