source_monitor 0.3.3 → 0.4.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +101 -58
  3. data/.claude/skills/sm-configure/SKILL.md +13 -2
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +33 -0
  5. data/.claude/skills/sm-host-setup/SKILL.md +15 -1
  6. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +33 -0
  7. data/.claude/skills/sm-job/SKILL.md +1 -1
  8. data/.vbw-planning/REQUIREMENTS.md +22 -0
  9. data/.vbw-planning/ROADMAP.md +125 -0
  10. data/.vbw-planning/STATE.md +43 -0
  11. data/.vbw-planning/config.json +3 -1
  12. data/.vbw-planning/discovery.json +3 -1
  13. data/.vbw-planning/phases/01-generator-steps/01-CONTEXT.md +33 -0
  14. data/.vbw-planning/phases/01-generator-steps/01-VERIFICATION.md +86 -0
  15. data/.vbw-planning/phases/01-generator-steps/PLAN-01-SUMMARY.md +61 -0
  16. data/.vbw-planning/phases/01-generator-steps/PLAN-01.md +380 -0
  17. data/.vbw-planning/phases/02-verification/02-VERIFICATION.md +78 -0
  18. data/.vbw-planning/phases/02-verification/PLAN-01-SUMMARY.md +46 -0
  19. data/.vbw-planning/phases/02-verification/PLAN-01.md +500 -0
  20. data/.vbw-planning/phases/03-docs-alignment/03-VERIFICATION.md +89 -0
  21. data/.vbw-planning/phases/03-docs-alignment/PLAN-01-SUMMARY.md +48 -0
  22. data/.vbw-planning/phases/03-docs-alignment/PLAN-01.md +456 -0
  23. data/.vbw-planning/phases/04-dashboard-ux/04-VERIFICATION.md +129 -0
  24. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01-SUMMARY.md +70 -0
  25. data/.vbw-planning/phases/04-dashboard-ux/PLAN-01.md +747 -0
  26. data/.vbw-planning/phases/05-active-storage-images/05-VERIFICATION.md +156 -0
  27. data/.vbw-planning/phases/05-active-storage-images/PLAN-01-SUMMARY.md +69 -0
  28. data/.vbw-planning/phases/05-active-storage-images/PLAN-01.md +455 -0
  29. data/.vbw-planning/phases/05-active-storage-images/PLAN-02-SUMMARY.md +39 -0
  30. data/.vbw-planning/phases/05-active-storage-images/PLAN-02.md +488 -0
  31. data/.vbw-planning/phases/06-netflix-feed-fix/06-VERIFICATION.md +100 -0
  32. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01-SUMMARY.md +37 -0
  33. data/.vbw-planning/phases/06-netflix-feed-fix/PLAN-01.md +345 -0
  34. data/CHANGELOG.md +31 -0
  35. data/Gemfile.lock +1 -1
  36. data/VERSION +1 -1
  37. data/app/assets/builds/source_monitor/application.css +9 -0
  38. data/app/helpers/source_monitor/application_helper.rb +38 -0
  39. data/app/jobs/source_monitor/download_content_images_job.rb +72 -0
  40. data/app/models/source_monitor/item_content.rb +2 -0
  41. data/app/views/source_monitor/dashboard/_recent_activity.html.erb +9 -0
  42. data/app/views/source_monitor/items/_details.html.erb +2 -2
  43. data/app/views/source_monitor/logs/index.html.erb +9 -0
  44. data/app/views/source_monitor/sources/_details.html.erb +2 -2
  45. data/app/views/source_monitor/sources/_row.html.erb +1 -1
  46. data/docs/setup.md +10 -1
  47. data/docs/troubleshooting.md +38 -7
  48. data/lib/generators/source_monitor/install/install_generator.rb +101 -0
  49. data/lib/source_monitor/configuration/http_settings.rb +7 -1
  50. data/lib/source_monitor/configuration/images_settings.rb +37 -0
  51. data/lib/source_monitor/configuration.rb +3 -1
  52. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +16 -7
  53. data/lib/source_monitor/dashboard/recent_activity.rb +1 -0
  54. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +15 -2
  55. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +13 -0
  56. data/lib/source_monitor/http.rb +23 -0
  57. data/lib/source_monitor/images/content_rewriter.rb +81 -0
  58. data/lib/source_monitor/images/downloader.rb +82 -0
  59. data/lib/source_monitor/logs/table_presenter.rb +25 -0
  60. data/lib/source_monitor/setup/procfile_patcher.rb +31 -0
  61. data/lib/source_monitor/setup/queue_config_patcher.rb +84 -0
  62. data/lib/source_monitor/setup/verification/recurring_schedule_verifier.rb +102 -0
  63. data/lib/source_monitor/setup/verification/runner.rb +1 -1
  64. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +1 -1
  65. data/lib/source_monitor/setup/workflow.rb +10 -0
  66. data/lib/source_monitor/version.rb +1 -1
  67. data/lib/source_monitor.rb +8 -0
  68. metadata +31 -1
@@ -0,0 +1,78 @@
1
+ # PLAN-01 Verification Report: recurring-schedule-verifier
2
+
3
+ **Verifier:** QA Agent (deep tier, 30 checks)
4
+ **Date:** 2026-02-11
5
+ **Verdict:** PASS
6
+
7
+ ---
8
+
9
+ ## Functional Checks (1-5)
10
+
11
+ | # | Check | Result | Details |
12
+ |---|-------|--------|---------|
13
+ | 1 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/` | PASS | 19 runs, 65 assertions, 0 failures, 0 errors |
14
+ | 2 | `bin/rails test` (full suite) | PASS | 874 runs, 2926 assertions, 0 failures, 0 errors |
15
+ | 3 | `bin/rubocop` (modified files) | PASS | 6 files inspected, 0 offenses |
16
+ | 4 | `bin/brakeman --no-pager` | PASS | 0 warnings, 0 errors |
17
+ | 5 | `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/runner_test.rb` | PASS | 2 runs, 8 assertions, 0 failures |
18
+
19
+ ---
20
+
21
+ ## Code Review (6-20)
22
+
23
+ | # | Check | Result | Details |
24
+ |---|-------|--------|---------|
25
+ | 6 | RecurringScheduleVerifier follows same pattern as SolidQueueVerifier | PASS | Same structure: constructor with DI defaults, `call` with guard clauses + branching + rescue, private helpers, Result factory methods. Module nesting matches: `SourceMonitor::Setup::Verification::RecurringScheduleVerifier` |
26
+ | 7 | Constructor uses dependency injection (`task_relation`, `connection`) | PASS | `def initialize(task_relation: default_task_relation, connection: default_connection)` -- keyword args with private default methods, identical pattern to SolidQueueVerifier's `process_relation:` / `connection:` |
27
+ | 8 | `call` method returns Result with proper key/name/status | PASS | All paths return `Result.new(key: :recurring_schedule, name: "Recurring Schedule", status: ...)` via `ok_result`, `warning_result`, `error_result` helpers |
28
+ | 9 | All 5 branches covered (ok, 2x warning, 2x error + rescue) | PASS | Lines 16 (missing gem error), 17 (missing table error), 22-23 (ok), 24-28 (warning: non-SM tasks), 29-33 (warning: no tasks), 35-39 (rescue error). Six distinct branches total (5 explicit + 1 rescue), all tested |
29
+ | 10 | SourceMonitor task detection logic (key prefix, class_name, command) | PASS | `source_monitor_tasks` method checks: `task.key.start_with?(SOURCE_MONITOR_KEY_PREFIX)` OR `task.class_name.to_s.start_with?(SOURCE_MONITOR_NAMESPACE)` OR `task.command.to_s.include?(SOURCE_MONITOR_NAMESPACE)`. `.to_s` on class_name/command handles nil safely |
30
+ | 11 | `frozen_string_literal` on new files | PASS | Both `recurring_schedule_verifier.rb` (line 1) and `recurring_schedule_verifier_test.rb` (line 1) have `# frozen_string_literal: true` |
31
+ | 12 | Private methods properly scoped | PASS | `private` keyword at line 42, followed by `attr_reader`, `default_task_relation`, `default_connection`, `tables_present?`, `all_tasks`, `source_monitor_tasks`, `missing_gem_result`, `missing_tables_result`, `ok_result`, `warning_result`, `error_result` -- all correctly private |
32
+ | 13 | SolidQueueVerifier remediation mentions Procfile.dev (REQ-20) | PASS | Line 24: `"Start a Solid Queue worker with \`bin/rails solid_queue:start\` or add \`jobs: bundle exec rake solid_queue:start\` to Procfile.dev and run \`bin/dev\`"` |
33
+ | 14 | Runner includes RecurringScheduleVerifier in default_verifiers | PASS | `runner.rb` line 21: `[ SolidQueueVerifier.new, RecurringScheduleVerifier.new, ActionCableVerifier.new ]` |
34
+ | 15 | Autoload entry in lib/source_monitor.rb | PASS | Line 174: `autoload :RecurringScheduleVerifier, "source_monitor/setup/verification/recurring_schedule_verifier"` -- correctly placed within the `module Verification` block |
35
+ | 16 | Tests use mocked/stubbed relations (no real DB) | PASS | `FakeTask` (Struct), `FakeTaskRelation` (custom class with `all`/`to_a`), `FakeConnection` (custom class with `table_exists?`) -- no ActiveRecord, no database queries |
36
+ | 17 | Tests cover all branches | PASS | 7 tests: ok (key prefix), ok (command), warning (non-SM tasks), warning (no tasks), error (missing gem), error (missing table), error (unexpected exception) |
37
+ | 18 | No hardcoded paths | PASS | Table name comes from `task_relation.table_name`, no filesystem paths. Constants are strings ("source_monitor_", "SourceMonitor::") which are correct domain identifiers, not paths |
38
+ | 19 | Error handling (rescue StandardError) | PASS | Line 35: `rescue StandardError => e` wraps the entire `call` body. Returns error_result with `e.message` interpolated. Test "rescues unexpected failures" covers this with a `raise "boom"` |
39
+ | 20 | Result key is `:recurring_schedule` | PASS | All three result helpers (`ok_result`, `warning_result`, `error_result`) use `key: :recurring_schedule` |
40
+
41
+ ---
42
+
43
+ ## Edge Cases (21-25)
44
+
45
+ | # | Check | Result | Details |
46
+ |---|-------|--------|---------|
47
+ | 21 | What if `SolidQueue::RecurringTask` doesn't exist? | PASS | `default_task_relation` returns `nil` via `defined?` guard. `call` hits `return missing_gem_result unless task_relation` on line 16. Test "errors when solid queue gem is missing" covers this with `task_relation: nil` |
48
+ | 22 | What if table exists but is empty? | PASS | `all_tasks` returns `[]`, `source_monitor_tasks([])` returns `[]`, falls through to `else` branch on line 29: "No recurring tasks are registered". Test "warns when no recurring tasks are registered" covers this |
49
+ | 23 | What if tasks exist but none match SourceMonitor? | PASS | `sm_tasks` is empty, `tasks.any?` is true, hits `elsif` on line 24: "Recurring tasks exist but none belong to SourceMonitor". Test "warns when tasks exist but none belong to source monitor" covers this |
50
+ | 24 | What if connection is nil? | PASS | `tables_present?` returns `false` on line 57 (`return false unless connection`), which triggers `missing_tables_result`. If `task_relation` is also nil, the missing gem guard on line 16 fires first. Both paths are safe |
51
+ | 25 | What if an exception is raised during verification? | PASS | `rescue StandardError => e` at line 35 catches any exception from `all_tasks`, `source_monitor_tasks`, or `tables_present?`. Test "rescues unexpected failures" triggers this with `def all = raise "boom"` |
52
+
53
+ ---
54
+
55
+ ## Requirements (26-30)
56
+
57
+ | # | Check | Result | Details |
58
+ |---|-------|--------|---------|
59
+ | 26 | REQ-19: RecurringScheduleVerifier checks recurring task registration | PASS | Verifier queries `SolidQueue::RecurringTask`, filters by key prefix / class_name / command namespace, returns ok/warning/error based on findings |
60
+ | 27 | REQ-20: SolidQueueVerifier mentions Procfile.dev | PASS | Remediation string includes `Procfile.dev` and `bin/dev`. Test assertion `assert_match(/Procfile\.dev/, result.remediation)` confirms |
61
+ | 28 | Test count increased (was 867, should be 874+) | PASS | 874 runs (was 867 before plan, +7 new RecurringScheduleVerifier tests) |
62
+ | 29 | No regressions in existing tests | PASS | Full suite: 874 runs, 2926 assertions, 0 failures, 0 errors, 0 skips |
63
+ | 30 | Verification integrates properly with Runner flow | PASS | Runner `default_verifiers` returns 3 verifiers in order: SolidQueue, RecurringSchedule, ActionCable. Runner test stubs all 3, asserts 3 results, confirms each called exactly once |
64
+
65
+ ---
66
+
67
+ ## Summary
68
+
69
+ - **30/30 checks passed**
70
+ - **0 issues found**
71
+ - Full test suite: 874 runs, 2926 assertions, 0 failures
72
+ - RuboCop: 0 offenses across all modified files
73
+ - Brakeman: 0 warnings
74
+ - RecurringScheduleVerifier follows established verifier patterns exactly
75
+ - All branches (6 paths) tested with appropriate fakes/stubs
76
+ - Both REQ-19 and REQ-20 satisfied
77
+
78
+ **VERDICT: PASS**
@@ -0,0 +1,46 @@
1
+ # PLAN-01 Summary: recurring-schedule-verifier
2
+
3
+ ## Status: COMPLETE
4
+
5
+ ## What Was Done
6
+
7
+ ### Task 1: Create RecurringScheduleVerifier
8
+ - Created `lib/source_monitor/setup/verification/recurring_schedule_verifier.rb`
9
+ - Follows exact same pattern as SolidQueueVerifier and ActionCableVerifier
10
+ - Constructor accepts `task_relation:` and `connection:` for dependency injection
11
+ - Detects SourceMonitor tasks by key prefix (`source_monitor_`), class_name namespace (`SourceMonitor::`), or command namespace
12
+ - Five outcomes: ok (SM tasks found), warning (tasks exist but no SM ones), warning (no tasks at all), error (gem missing), error (table missing), plus rescue for unexpected failures
13
+
14
+ ### Task 2: Enhance SolidQueueVerifier Remediation
15
+ - Updated remediation message in `solid_queue_verifier.rb` line 24 to mention `Procfile.dev` and `bin/dev` workflow (REQ-20)
16
+ - Updated test assertion to verify Procfile.dev appears in remediation
17
+
18
+ ### Task 3: Add RecurringScheduleVerifier Tests
19
+ - Created `test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb`
20
+ - 7 tests covering all branches: ok (by key prefix), ok (by command), warning (non-SM tasks), warning (no tasks), error (missing gem), error (missing table), error (unexpected exception)
21
+
22
+ ### Task 4: Wire Into Runner + Autoload
23
+ - Added `RecurringScheduleVerifier.new` to `Runner#default_verifiers` between SolidQueue and ActionCable
24
+ - Added autoload declaration in `lib/source_monitor.rb` Verification module block
25
+ - Updated runner_test.rb to stub 3 verifiers and assert 3 results
26
+
27
+ ### Task 5: Full Suite Verification
28
+ - 874 runs, 2926 assertions, 0 failures, 0 errors
29
+ - RuboCop: 0 offenses
30
+ - Brakeman: 0 warnings
31
+
32
+ ## Files Modified
33
+ - `lib/source_monitor/setup/verification/recurring_schedule_verifier.rb` (new)
34
+ - `lib/source_monitor/setup/verification/solid_queue_verifier.rb` (remediation update)
35
+ - `lib/source_monitor/setup/verification/runner.rb` (added to default_verifiers)
36
+ - `lib/source_monitor.rb` (autoload declaration)
37
+ - `test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb` (new, 7 tests)
38
+ - `test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb` (Procfile.dev assertion)
39
+ - `test/lib/source_monitor/setup/verification/runner_test.rb` (3-verifier expectation)
40
+
41
+ ## Commit
42
+ - `d03e3b9` feat(verification): add RecurringScheduleVerifier and enhance SolidQueue remediation
43
+
44
+ ## Requirements Satisfied
45
+ - REQ-19: RecurringScheduleVerifier checks SolidQueue recurring tasks registration
46
+ - REQ-20: SolidQueueVerifier remediation mentions Procfile.dev and bin/dev
@@ -0,0 +1,500 @@
1
+ ---
2
+ phase: 2
3
+ plan: "01"
4
+ title: recurring-schedule-verifier
5
+ type: execute
6
+ wave: 1
7
+ depends_on: []
8
+ cross_phase_deps:
9
+ - phase: 1
10
+ artifact: "lib/source_monitor/setup/verification/solid_queue_verifier.rb"
11
+ reason: "Phase 2 modifies this file's remediation message (REQ-20)"
12
+ autonomous: true
13
+ effort_override: thorough
14
+ skills_used: []
15
+ files_modified:
16
+ - lib/source_monitor/setup/verification/recurring_schedule_verifier.rb
17
+ - lib/source_monitor/setup/verification/solid_queue_verifier.rb
18
+ - lib/source_monitor/setup/verification/runner.rb
19
+ - lib/source_monitor.rb
20
+ - test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb
21
+ - test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb
22
+ - test/lib/source_monitor/setup/verification/runner_test.rb
23
+ must_haves:
24
+ truths:
25
+ - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb` exits 0 with 0 failures"
26
+ - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb` exits 0 with 0 failures"
27
+ - "Running `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/runner_test.rb` exits 0 with 0 failures"
28
+ - "Running `bin/rubocop lib/source_monitor/setup/verification/recurring_schedule_verifier.rb lib/source_monitor/setup/verification/solid_queue_verifier.rb lib/source_monitor/setup/verification/runner.rb` exits 0 with no offenses"
29
+ - "Running `bin/rails test` exits 0 with 867+ runs and 0 failures"
30
+ artifacts:
31
+ - path: "lib/source_monitor/setup/verification/recurring_schedule_verifier.rb"
32
+ provides: "Verifier that checks SolidQueue recurring tasks registration (REQ-19)"
33
+ contains: "class RecurringScheduleVerifier"
34
+ - path: "lib/source_monitor/setup/verification/solid_queue_verifier.rb"
35
+ provides: "Enhanced remediation mentioning Procfile.dev (REQ-20)"
36
+ contains: "Procfile.dev"
37
+ - path: "lib/source_monitor/setup/verification/runner.rb"
38
+ provides: "Runner wires RecurringScheduleVerifier into default_verifiers"
39
+ contains: "RecurringScheduleVerifier"
40
+ - path: "lib/source_monitor.rb"
41
+ provides: "Autoload declaration for RecurringScheduleVerifier"
42
+ contains: "autoload :RecurringScheduleVerifier"
43
+ - path: "test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb"
44
+ provides: "Tests covering all RecurringScheduleVerifier branches"
45
+ contains: "class RecurringScheduleVerifierTest"
46
+ key_links:
47
+ - from: "recurring_schedule_verifier.rb"
48
+ to: "REQ-19"
49
+ via: "Checks whether recurring tasks are registered with SolidQueue dispatchers"
50
+ - from: "solid_queue_verifier.rb#warning_result remediation"
51
+ to: "REQ-20"
52
+ via: "Remediation now suggests Procfile.dev for bin/dev users"
53
+ - from: "runner.rb#default_verifiers"
54
+ to: "recurring_schedule_verifier.rb"
55
+ via: "Runner includes RecurringScheduleVerifier in the default verifier set"
56
+ ---
57
+ <objective>
58
+ Add a RecurringScheduleVerifier to the verification suite that checks whether recurring tasks are registered with Solid Queue (REQ-19), and enhance the SolidQueueVerifier remediation message to suggest Procfile.dev when workers are not detected (REQ-20). Wire the new verifier into the Runner and autoload system.
59
+ </objective>
60
+ <context>
61
+ @lib/source_monitor/setup/verification/solid_queue_verifier.rb -- The existing verifier to enhance. Constructor accepts `process_relation:`, `connection:`, `clock:` with defaults. The `call` method returns a Result via helper methods `ok_result`, `warning_result`, `error_result`. Key change: line 24's warning_result remediation string must be updated to mention Procfile.dev. Follow the exact same pattern for the new verifier. Key: `:solid_queue`, name: `"Solid Queue"`.
62
+
63
+ @lib/source_monitor/setup/verification/action_cable_verifier.rb -- Pattern reference for verifier design. Shows constructor dependency injection, `call` method with case/when branching, rescue StandardError, and Result helpers. The new RecurringScheduleVerifier should follow the same structure.
64
+
65
+ @lib/source_monitor/setup/verification/result.rb -- Result struct with `key`, `name`, `status`, `details`, `remediation` and status predicates (`ok?`, `warning?`, `error?`). Summary aggregates results. The new verifier should use key: `:recurring_schedule`, name: `"Recurring Schedule"`.
66
+
67
+ @lib/source_monitor/setup/verification/runner.rb -- Orchestrator with `default_verifiers` returning an array. Currently `[SolidQueueVerifier.new, ActionCableVerifier.new]`. Add `RecurringScheduleVerifier.new` to this array.
68
+
69
+ @test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb -- Test pattern: uses FakeRelation and FakeConnection structs, tests all branches (ok, warning, error for missing gem, missing tables, unexpected failure). The "warns when no recent workers" test should be updated to assert the new remediation mentions Procfile.dev.
70
+
71
+ @test/lib/source_monitor/setup/verification/runner_test.rb -- Tests Runner with stub verifiers. The "uses default verifiers" test stubs SolidQueueVerifier and ActionCableVerifier via `.stub(:new, ...)`. Must add a third stub for RecurringScheduleVerifier and update assertions to expect 3 results.
72
+
73
+ @lib/source_monitor.rb lines 169-177 -- Autoload declarations for Setup::Verification module. Add `autoload :RecurringScheduleVerifier` here.
74
+
75
+ @lib/source_monitor/engine.rb lines 54-60 -- Shows how `SolidQueue::RecurringTask` is used elsewhere in the codebase. The model has columns: key, class_name, command, schedule, queue_name, static. Tasks with `class_name` starting with "SourceMonitor::" or `command` containing "SourceMonitor::" are SourceMonitor-owned entries.
76
+
77
+ @test/dummy/config/recurring.yml -- Shows the 5 recurring tasks configured for the dummy app: source_monitor_schedule_fetches, source_monitor_schedule_scrapes, source_monitor_item_cleanup, source_monitor_log_cleanup, clear_solid_queue_finished_jobs. The first 4 are SourceMonitor-owned (keys start with `source_monitor_` or class_name/command references SourceMonitor::).
78
+
79
+ **Rationale:** The RecurringScheduleVerifier checks that recurring tasks (defined in recurring.yml) are actually loaded into the solid_queue_recurring_tasks table. This catches the common failure where a user has the YAML file but the dispatcher is not configured with `recurring_schedule: config/recurring.yml`, so tasks never get registered. The verifier queries `SolidQueue::RecurringTask` and looks for entries whose key starts with `source_monitor_` OR whose class_name/command references `SourceMonitor::`.
80
+
81
+ **Key design decisions:**
82
+ 1. Check for `SolidQueue::RecurringTask` availability (same pattern as SolidQueueVerifier checking Process)
83
+ 2. Check table existence before querying (same pattern)
84
+ 3. Query for any recurring tasks, then filter for SourceMonitor-specific ones
85
+ 4. Four outcomes: (a) ok if SM tasks found, (b) warning if tasks exist but no SM ones, (c) warning if no tasks at all, (d) error if SolidQueue unavailable
86
+ 5. SourceMonitor task detection: key starts with `source_monitor_` OR class_name starts with `SourceMonitor::` OR command contains `SourceMonitor::`
87
+ 6. Inject `task_relation:` and `connection:` via constructor for testability
88
+ </context>
89
+ <tasks>
90
+ <task type="auto">
91
+ <name>create-recurring-schedule-verifier</name>
92
+ <files>
93
+ lib/source_monitor/setup/verification/recurring_schedule_verifier.rb
94
+ </files>
95
+ <action>
96
+ Create a new file `lib/source_monitor/setup/verification/recurring_schedule_verifier.rb` following the exact pattern of SolidQueueVerifier and ActionCableVerifier.
97
+
98
+ ```ruby
99
+ # frozen_string_literal: true
100
+
101
+ module SourceMonitor
102
+ module Setup
103
+ module Verification
104
+ class RecurringScheduleVerifier
105
+ SOURCE_MONITOR_KEY_PREFIX = "source_monitor_"
106
+ SOURCE_MONITOR_NAMESPACE = "SourceMonitor::"
107
+
108
+ def initialize(task_relation: default_task_relation, connection: default_connection)
109
+ @task_relation = task_relation
110
+ @connection = connection
111
+ end
112
+
113
+ def call
114
+ return missing_gem_result unless task_relation
115
+ return missing_tables_result unless tables_present?
116
+
117
+ tasks = all_tasks
118
+ sm_tasks = source_monitor_tasks(tasks)
119
+
120
+ if sm_tasks.any?
121
+ ok_result("#{sm_tasks.size} SourceMonitor recurring task(s) registered")
122
+ elsif tasks.any?
123
+ warning_result(
124
+ "Recurring tasks exist but none belong to SourceMonitor",
125
+ "Add SourceMonitor entries to config/recurring.yml and ensure the dispatcher has `recurring_schedule: config/recurring.yml`"
126
+ )
127
+ else
128
+ warning_result(
129
+ "No recurring tasks are registered with Solid Queue",
130
+ "Configure a dispatcher with `recurring_schedule: config/recurring.yml` in config/queue.yml and ensure recurring.yml contains SourceMonitor task entries"
131
+ )
132
+ end
133
+ rescue StandardError => e
134
+ error_result(
135
+ "Recurring schedule verification failed: #{e.message}",
136
+ "Verify Solid Queue migrations are up to date and the dispatcher is configured with recurring_schedule"
137
+ )
138
+ end
139
+
140
+ private
141
+
142
+ attr_reader :task_relation, :connection
143
+
144
+ def default_task_relation
145
+ SolidQueue::RecurringTask if defined?(SolidQueue::RecurringTask)
146
+ end
147
+
148
+ def default_connection
149
+ SolidQueue::RecurringTask.connection if defined?(SolidQueue::RecurringTask)
150
+ rescue StandardError
151
+ nil
152
+ end
153
+
154
+ def tables_present?
155
+ return false unless connection
156
+
157
+ connection.table_exists?(task_relation.table_name)
158
+ end
159
+
160
+ def all_tasks
161
+ task_relation.all.to_a
162
+ end
163
+
164
+ def source_monitor_tasks(tasks)
165
+ tasks.select do |task|
166
+ task.key.start_with?(SOURCE_MONITOR_KEY_PREFIX) ||
167
+ task.class_name.to_s.start_with?(SOURCE_MONITOR_NAMESPACE) ||
168
+ task.command.to_s.include?(SOURCE_MONITOR_NAMESPACE)
169
+ end
170
+ end
171
+
172
+ def missing_gem_result
173
+ error_result(
174
+ "Solid Queue gem is not available",
175
+ "Add `solid_queue` to your Gemfile and bundle install"
176
+ )
177
+ end
178
+
179
+ def missing_tables_result
180
+ error_result(
181
+ "Solid Queue recurring tasks table is missing",
182
+ "Run `rails solid_queue:install` or copy the engine's Solid Queue migration"
183
+ )
184
+ end
185
+
186
+ def ok_result(details)
187
+ Result.new(key: :recurring_schedule, name: "Recurring Schedule", status: :ok, details: details)
188
+ end
189
+
190
+ def warning_result(details, remediation)
191
+ Result.new(key: :recurring_schedule, name: "Recurring Schedule", status: :warning, details: details, remediation: remediation)
192
+ end
193
+
194
+ def error_result(details, remediation)
195
+ Result.new(key: :recurring_schedule, name: "Recurring Schedule", status: :error, details: details, remediation: remediation)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ Key design points:
204
+ - Constructor accepts `task_relation:` and `connection:` for dependency injection (testability)
205
+ - Defaults to `SolidQueue::RecurringTask` if available
206
+ - `all_tasks` fetches all recurring tasks, then `source_monitor_tasks` filters by key prefix, class_name namespace, or command namespace
207
+ - Three-tier result: ok (SM tasks found), warning (tasks exist but no SM ones OR no tasks at all), error (gem/table missing or unexpected failure)
208
+ - Same rescue StandardError pattern as other verifiers
209
+ </action>
210
+ <verify>
211
+ Read the created file and confirm: (a) class is in the correct module nesting, (b) constructor follows dependency injection pattern, (c) `call` method handles all 5 outcomes (missing gem, missing tables, SM tasks found, non-SM tasks only, no tasks), (d) helper methods are private, (e) Result key is `:recurring_schedule`.
212
+ </verify>
213
+ <done>
214
+ RecurringScheduleVerifier created with full branch coverage: missing gem, missing tables, SM tasks found (ok), non-SM tasks only (warning), no tasks (warning), unexpected error.
215
+ </done>
216
+ </task>
217
+ <task type="auto">
218
+ <name>enhance-solid-queue-verifier-remediation</name>
219
+ <files>
220
+ lib/source_monitor/setup/verification/solid_queue_verifier.rb
221
+ test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb
222
+ </files>
223
+ <action>
224
+ **Modify `lib/source_monitor/setup/verification/solid_queue_verifier.rb`:**
225
+
226
+ Change the remediation string on line 24 from:
227
+ ```ruby
228
+ "Start a Solid Queue worker with `bin/rails solid_queue:start` and ensure it stays running"
229
+ ```
230
+ to:
231
+ ```ruby
232
+ "Start a Solid Queue worker with `bin/rails solid_queue:start` or add `jobs: bundle exec rake solid_queue:start` to Procfile.dev and run `bin/dev`"
233
+ ```
234
+
235
+ This is a single-line change in the `call` method's warning_result call (the "no recent workers" branch).
236
+
237
+ **Modify `test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb`:**
238
+
239
+ Update the "warns when no recent workers" test to also assert the remediation message mentions Procfile.dev:
240
+ ```ruby
241
+ assert_match(/Procfile\.dev/, result.remediation)
242
+ ```
243
+
244
+ Add this assertion after the existing `assert_match(/No Solid Queue workers/, result.details)` line.
245
+ </action>
246
+ <verify>
247
+ Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb` -- all 5 tests pass. Run `bin/rubocop lib/source_monitor/setup/verification/solid_queue_verifier.rb` -- 0 offenses. Grep for "Procfile.dev" in the verifier file confirms the new remediation text.
248
+ </verify>
249
+ <done>
250
+ SolidQueueVerifier remediation now mentions Procfile.dev with the `bin/dev` workflow. Existing test updated to assert the new message content. REQ-20 satisfied.
251
+ </done>
252
+ </task>
253
+ <task type="auto">
254
+ <name>add-recurring-schedule-verifier-tests</name>
255
+ <files>
256
+ test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb
257
+ </files>
258
+ <action>
259
+ Create a new test file following the exact pattern from `solid_queue_verifier_test.rb`:
260
+
261
+ ```ruby
262
+ # frozen_string_literal: true
263
+
264
+ require "test_helper"
265
+
266
+ module SourceMonitor
267
+ module Setup
268
+ module Verification
269
+ class RecurringScheduleVerifierTest < ActiveSupport::TestCase
270
+ # Fake task struct matching SolidQueue::RecurringTask's interface
271
+ FakeTask = Struct.new(:key, :class_name, :command, keyword_init: true)
272
+
273
+ # Fake relation that returns tasks and supports table_name
274
+ class FakeTaskRelation
275
+ attr_reader :table_name
276
+
277
+ def initialize(tasks, table_name: "solid_queue_recurring_tasks")
278
+ @tasks = tasks
279
+ @table_name = table_name
280
+ end
281
+
282
+ def all
283
+ self
284
+ end
285
+
286
+ def to_a
287
+ @tasks
288
+ end
289
+ end
290
+
291
+ class FakeConnection
292
+ def initialize(tables: [])
293
+ @tables = tables
294
+ end
295
+
296
+ def table_exists?(name)
297
+ @tables.include?(name)
298
+ end
299
+ end
300
+
301
+ test "returns ok when source monitor recurring tasks are registered" do
302
+ tasks = [
303
+ FakeTask.new(key: "source_monitor_schedule_fetches", class_name: "SourceMonitor::ScheduleFetchesJob", command: nil),
304
+ FakeTask.new(key: "source_monitor_item_cleanup", class_name: "SourceMonitor::ItemCleanupJob", command: nil)
305
+ ]
306
+ relation = FakeTaskRelation.new(tasks)
307
+ connection = FakeConnection.new(tables: ["solid_queue_recurring_tasks"])
308
+
309
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
310
+
311
+ assert_equal :ok, result.status
312
+ assert_match(/2 SourceMonitor recurring task/, result.details)
313
+ end
314
+
315
+ test "returns ok when source monitor tasks detected by command" do
316
+ tasks = [
317
+ FakeTask.new(key: "source_monitor_schedule_scrapes", class_name: nil, command: "SourceMonitor::Scraping::Scheduler.run(limit: 100)")
318
+ ]
319
+ relation = FakeTaskRelation.new(tasks)
320
+ connection = FakeConnection.new(tables: ["solid_queue_recurring_tasks"])
321
+
322
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
323
+
324
+ assert_equal :ok, result.status
325
+ end
326
+
327
+ test "warns when tasks exist but none belong to source monitor" do
328
+ tasks = [
329
+ FakeTask.new(key: "other_app_cleanup", class_name: "OtherApp::CleanupJob", command: nil)
330
+ ]
331
+ relation = FakeTaskRelation.new(tasks)
332
+ connection = FakeConnection.new(tables: ["solid_queue_recurring_tasks"])
333
+
334
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
335
+
336
+ assert_equal :warning, result.status
337
+ assert_match(/none belong to SourceMonitor/, result.details)
338
+ assert_match(/recurring\.yml/, result.remediation)
339
+ end
340
+
341
+ test "warns when no recurring tasks are registered" do
342
+ relation = FakeTaskRelation.new([])
343
+ connection = FakeConnection.new(tables: ["solid_queue_recurring_tasks"])
344
+
345
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
346
+
347
+ assert_equal :warning, result.status
348
+ assert_match(/No recurring tasks are registered/, result.details)
349
+ assert_match(/recurring_schedule/, result.remediation)
350
+ end
351
+
352
+ test "errors when solid queue gem is missing" do
353
+ result = RecurringScheduleVerifier.new(task_relation: nil, connection: nil).call
354
+
355
+ assert_equal :error, result.status
356
+ assert_match(/gem is not available/, result.details)
357
+ end
358
+
359
+ test "errors when recurring tasks table is missing" do
360
+ relation = FakeTaskRelation.new([], table_name: "solid_queue_recurring_tasks")
361
+ connection = FakeConnection.new(tables: [])
362
+
363
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
364
+
365
+ assert_equal :error, result.status
366
+ assert_match(/table is missing/, result.details)
367
+ end
368
+
369
+ test "rescues unexpected failures and reports remediation" do
370
+ relation = Class.new do
371
+ def table_name = "solid_queue_recurring_tasks"
372
+ def all = raise "boom"
373
+ end.new
374
+ connection = FakeConnection.new(tables: ["solid_queue_recurring_tasks"])
375
+
376
+ result = RecurringScheduleVerifier.new(task_relation: relation, connection: connection).call
377
+
378
+ assert_equal :error, result.status
379
+ assert_match(/verification failed/i, result.details)
380
+ assert_match(/dispatcher/, result.remediation)
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+ ```
387
+
388
+ 7 tests covering all branches: ok (by key prefix), ok (by command), warning (non-SM tasks), warning (no tasks), error (missing gem), error (missing table), error (unexpected exception).
389
+ </action>
390
+ <verify>
391
+ Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb` -- all 7 tests pass. Run `bin/rubocop test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb` -- 0 offenses.
392
+ </verify>
393
+ <done>
394
+ 7 tests covering all RecurringScheduleVerifier branches pass. RuboCop clean.
395
+ </done>
396
+ </task>
397
+ <task type="auto">
398
+ <name>wire-into-runner-and-autoload</name>
399
+ <files>
400
+ lib/source_monitor/setup/verification/runner.rb
401
+ lib/source_monitor.rb
402
+ test/lib/source_monitor/setup/verification/runner_test.rb
403
+ </files>
404
+ <action>
405
+ **Modify `lib/source_monitor/setup/verification/runner.rb`:**
406
+
407
+ Add `RecurringScheduleVerifier.new` to the `default_verifiers` array (line 21). The array should become:
408
+ ```ruby
409
+ def default_verifiers
410
+ [ SolidQueueVerifier.new, RecurringScheduleVerifier.new, ActionCableVerifier.new ]
411
+ end
412
+ ```
413
+
414
+ Place RecurringScheduleVerifier between SolidQueue and ActionCable -- it logically groups with SolidQueue (both check SQ state) and should run after the worker heartbeat check but before the ActionCable check.
415
+
416
+ **Modify `lib/source_monitor.rb`:**
417
+
418
+ Add the autoload declaration in the `module Verification` block (around line 174), after the ActionCableVerifier line:
419
+ ```ruby
420
+ autoload :RecurringScheduleVerifier, "source_monitor/setup/verification/recurring_schedule_verifier"
421
+ ```
422
+
423
+ **Modify `test/lib/source_monitor/setup/verification/runner_test.rb`:**
424
+
425
+ Update the "uses default verifiers" test:
426
+ 1. Add a recurring_result: `recurring_result = Result.new(key: :recurring_schedule, name: "Recurring Schedule", status: :ok, details: "ok")`
427
+ 2. Add a recurring_double: `recurring_double = verifier_double.new(recurring_result)`
428
+ 3. Add a third stub inside the existing stub blocks: `RecurringScheduleVerifier.stub(:new, ->(*) { recurring_double }) do`
429
+ 4. Update assertion: `assert_equal 3, summary.results.size`
430
+ 5. Add: `assert_equal 1, recurring_double.calls`
431
+ </action>
432
+ <verify>
433
+ Run `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/runner_test.rb` -- all tests pass. Grep for `RecurringScheduleVerifier` in runner.rb and source_monitor.rb confirms wiring.
434
+ </verify>
435
+ <done>
436
+ RecurringScheduleVerifier wired into Runner.default_verifiers and autoloaded from lib/source_monitor.rb. Runner test updated to expect 3 verifiers.
437
+ </done>
438
+ </task>
439
+ <task type="auto">
440
+ <name>full-suite-verification</name>
441
+ <files>
442
+ lib/source_monitor/setup/verification/recurring_schedule_verifier.rb
443
+ lib/source_monitor/setup/verification/solid_queue_verifier.rb
444
+ lib/source_monitor/setup/verification/runner.rb
445
+ test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb
446
+ test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb
447
+ test/lib/source_monitor/setup/verification/runner_test.rb
448
+ </files>
449
+ <action>
450
+ Run the full test suite and linting to confirm no regressions:
451
+
452
+ 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/` -- all verification tests pass (existing + new)
453
+ 2. `bin/rails test` -- full suite passes with 867+ runs and 0 failures
454
+ 3. `bin/rubocop lib/source_monitor/setup/verification/ test/lib/source_monitor/setup/verification/` -- zero offenses
455
+ 4. `bin/brakeman --no-pager` -- zero warnings
456
+ 5. Review the final state of all modified files to confirm:
457
+ - RecurringScheduleVerifier follows the exact same pattern as SolidQueueVerifier
458
+ - SolidQueueVerifier remediation mentions Procfile.dev
459
+ - Runner.default_verifiers includes all 3 verifiers
460
+ - Autoload declaration is in the correct module block
461
+ - All tests cover the expected branches
462
+
463
+ If any test failures or RuboCop offenses are found, fix them before completing.
464
+ </action>
465
+ <verify>
466
+ `bin/rails test` exits 0 with 867+ runs, 0 failures. `bin/rubocop` exits 0 with 0 offenses. `bin/brakeman --no-pager` exits 0 with 0 warnings.
467
+ </verify>
468
+ <done>
469
+ Full test suite passes. RuboCop clean. Brakeman clean. All REQ-19, REQ-20 acceptance criteria met.
470
+ </done>
471
+ </task>
472
+ </tasks>
473
+ <verification>
474
+ 1. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/recurring_schedule_verifier_test.rb` -- 7 tests pass
475
+ 2. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/solid_queue_verifier_test.rb` -- 5 tests pass with updated assertion
476
+ 3. `PARALLEL_WORKERS=1 bin/rails test test/lib/source_monitor/setup/verification/runner_test.rb` -- 2 tests pass with 3-verifier expectation
477
+ 4. `bin/rails test` -- 867+ runs, 0 failures
478
+ 5. `bin/rubocop` -- 0 offenses
479
+ 6. `bin/brakeman --no-pager` -- 0 warnings
480
+ 7. `grep -n 'class RecurringScheduleVerifier' lib/source_monitor/setup/verification/recurring_schedule_verifier.rb` returns a match
481
+ 8. `grep -n 'Procfile.dev' lib/source_monitor/setup/verification/solid_queue_verifier.rb` returns a match
482
+ 9. `grep -n 'RecurringScheduleVerifier' lib/source_monitor/setup/verification/runner.rb` returns a match
483
+ 10. `grep -n 'RecurringScheduleVerifier' lib/source_monitor.rb` returns a match
484
+ </verification>
485
+ <success_criteria>
486
+ - RecurringScheduleVerifier returns ok when SourceMonitor recurring tasks are registered (REQ-19)
487
+ - RecurringScheduleVerifier warns when recurring tasks exist but none belong to SourceMonitor (REQ-19)
488
+ - RecurringScheduleVerifier warns when no recurring tasks are registered at all (REQ-19)
489
+ - RecurringScheduleVerifier errors when SolidQueue gem is missing (REQ-19)
490
+ - RecurringScheduleVerifier errors when recurring tasks table is missing (REQ-19)
491
+ - SolidQueueVerifier remediation mentions Procfile.dev and bin/dev (REQ-20)
492
+ - RecurringScheduleVerifier is included in Runner.default_verifiers
493
+ - RecurringScheduleVerifier is autoloaded from lib/source_monitor.rb
494
+ - All existing tests continue to pass (no regressions)
495
+ - 7+ new RecurringScheduleVerifier tests cover all branches
496
+ - RuboCop clean, Brakeman clean
497
+ </success_criteria>
498
+ <output>
499
+ .vbw-planning/phases/02-verification/PLAN-01-SUMMARY.md
500
+ </output>