source_monitor 0.13.1 → 0.15.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +1 -0
  3. data/.claude/skills/sm-configure/SKILL.md +8 -1
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +11 -0
  5. data/.claude/skills/sm-host-setup/SKILL.md +13 -3
  6. data/.claude/skills/sm-host-setup/reference/initializer-template.md +11 -0
  7. data/.claude/skills/sm-host-setup/reference/setup-checklist.md +9 -1
  8. data/.claude/skills/sm-upgrade/reference/version-history.md +26 -0
  9. data/AGENTS.md +145 -38
  10. data/CHANGELOG.md +29 -0
  11. data/CLAUDE.md +6 -116
  12. data/Gemfile.lock +86 -86
  13. data/README.md +3 -3
  14. data/VERSION +1 -1
  15. data/app/controllers/source_monitor/application_controller.rb +73 -14
  16. data/app/views/layouts/source_monitor/application.html.erb +6 -0
  17. data/docs/configuration.md +18 -1
  18. data/docs/deployment.md +1 -1
  19. data/docs/goals/engine-hardening/.goalbuddy-board/app.js +543 -0
  20. data/docs/goals/engine-hardening/.goalbuddy-board/goalbuddy-mark.png +0 -0
  21. data/docs/goals/engine-hardening/.goalbuddy-board/index.html +111 -0
  22. data/docs/goals/engine-hardening/.goalbuddy-board/styles.css +991 -0
  23. data/docs/goals/engine-hardening/goal.md +97 -0
  24. data/docs/goals/engine-hardening/notes/T001-spec-validation.md +37 -0
  25. data/docs/goals/engine-hardening/state.yaml +324 -0
  26. data/docs/setup.md +4 -4
  27. data/docs/upgrade.md +41 -0
  28. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +10 -0
  29. data/lib/source_monitor/configuration/authentication_settings.rb +5 -1
  30. data/lib/source_monitor/security/authentication.rb +10 -0
  31. data/lib/source_monitor/version.rb +1 -1
  32. data/source_monitor.gemspec +8 -3
  33. metadata +10 -67
  34. data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +0 -15
  35. data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +0 -34
  36. data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +0 -49
  37. data/.claude/agents/rails-concern.md +0 -464
  38. data/.claude/agents/rails-controller.md +0 -424
  39. data/.claude/agents/rails-hotwire.md +0 -446
  40. data/.claude/agents/rails-implement.md +0 -374
  41. data/.claude/agents/rails-job.md +0 -334
  42. data/.claude/agents/rails-lint.md +0 -294
  43. data/.claude/agents/rails-mailer.md +0 -371
  44. data/.claude/agents/rails-migration.md +0 -449
  45. data/.claude/agents/rails-model.md +0 -420
  46. data/.claude/agents/rails-policy.md +0 -443
  47. data/.claude/agents/rails-presenter.md +0 -427
  48. data/.claude/agents/rails-query.md +0 -412
  49. data/.claude/agents/rails-review.md +0 -490
  50. data/.claude/agents/rails-service.md +0 -458
  51. data/.claude/agents/rails-state-records.md +0 -465
  52. data/.claude/agents/rails-tdd.md +0 -314
  53. data/.claude/agents/rails-test.md +0 -441
  54. data/.claude/agents/rails-view-component.md +0 -418
  55. data/.claude/commands/rails-audit.md +0 -77
  56. data/.claude/commands/release.md +0 -366
  57. data/.claude/hooks/block-secrets.sh +0 -52
  58. data/.claude/settings.json +0 -85
  59. data/.claude/skills/action-cable-patterns/SKILL.md +0 -296
  60. data/.claude/skills/action-mailer-patterns/SKILL.md +0 -295
  61. data/.claude/skills/active-storage-setup/SKILL.md +0 -311
  62. data/.claude/skills/api-versioning/SKILL.md +0 -294
  63. data/.claude/skills/authentication-flow/SKILL.md +0 -335
  64. data/.claude/skills/authentication-flow/reference/current.md +0 -248
  65. data/.claude/skills/authentication-flow/reference/passwordless.md +0 -253
  66. data/.claude/skills/authentication-flow/reference/sessions.md +0 -201
  67. data/.claude/skills/authorization-pundit/SKILL.md +0 -462
  68. data/.claude/skills/caching-strategies/SKILL.md +0 -350
  69. data/.claude/skills/database-migrations/SKILL.md +0 -354
  70. data/.claude/skills/form-object-patterns/SKILL.md +0 -399
  71. data/.claude/skills/hotwire-patterns/SKILL.md +0 -247
  72. data/.claude/skills/hotwire-patterns/reference/stimulus.md +0 -307
  73. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +0 -112
  74. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +0 -158
  75. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +0 -218
  76. data/.claude/skills/i18n-patterns/SKILL.md +0 -320
  77. data/.claude/skills/install/SKILL.md +0 -367
  78. data/.claude/skills/performance-optimization/SKILL.md +0 -311
  79. data/.claude/skills/rails-architecture/SKILL.md +0 -259
  80. data/.claude/skills/rails-architecture/reference/error-handling.md +0 -333
  81. data/.claude/skills/rails-architecture/reference/event-tracking.md +0 -142
  82. data/.claude/skills/rails-architecture/reference/layer-interactions.md +0 -417
  83. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +0 -152
  84. data/.claude/skills/rails-architecture/reference/query-patterns.md +0 -342
  85. data/.claude/skills/rails-architecture/reference/service-patterns.md +0 -286
  86. data/.claude/skills/rails-architecture/reference/state-records.md +0 -250
  87. data/.claude/skills/rails-architecture/reference/testing-strategy.md +0 -326
  88. data/.claude/skills/rails-concern/SKILL.md +0 -399
  89. data/.claude/skills/rails-controller/SKILL.md +0 -336
  90. data/.claude/skills/rails-model-generator/SKILL.md +0 -321
  91. data/.claude/skills/rails-model-generator/reference/validations.md +0 -298
  92. data/.claude/skills/rails-presenter/SKILL.md +0 -274
  93. data/.claude/skills/rails-query-object/SKILL.md +0 -289
  94. data/.claude/skills/rails-service-object/SKILL.md +0 -349
  95. data/.claude/skills/solid-queue-setup/SKILL.md +0 -307
  96. data/.claude/skills/tdd-cycle/SKILL.md +0 -359
  97. data/.claude/skills/viewcomponent-patterns/SKILL.md +0 -333
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d863378a0fb5338b1b1fe89c40c0c0ab9705b3ce60c45889eabf1ed626d27cf1
4
- data.tar.gz: d3a56da362430857991b85ab2b24ef10b5e2641dc312ecbe2a302bdca1a8352b
3
+ metadata.gz: 55bf050a9f74f18ed354d7d751f868bc98dc8bc5da59c3da6ce5e0aa0cedc833
4
+ data.tar.gz: 37a5906b19541de00ef3b860f266a1c7a12e737da58d1a7b80cb806a191936d5
5
5
  SHA512:
6
- metadata.gz: 885cf285ecc91bf09f2bca2bd9f384cea7f36e1c966a0a74a20d7f87a9df8f69da79dd8363762111c3e206dfe1668ddbefc46cdda71c4e83eaeb138099e74a25
7
- data.tar.gz: b38579d624e4e66f5f051ce3aab2e61249fb549310e73e1959cdc0d67aef7fb63ff7c18232563269b2e826c5eb08f2a01bb5cf78f992df90bba3a0649d4d7a21
6
+ metadata.gz: 0c0109e46a1ec8691592fd304095c833d5d78b9091ffa216e72011040a8683be820016dd51b4a2185d17b9780761410918297f9f4fdeb2fd31d1e952575fbf99
7
+ data.tar.gz: 9016293bb6d6339026ed811689db2a4f1b87cd90fb60bb7f270113fc698c9714f9935f2aec704c1581b3ef6b61137b2df5c5b21e0ecd8b2bea69be987152057f
@@ -157,6 +157,7 @@ Has `reset!` method. The `adapter=` setter validates against `VALID_ADAPTERS`.
157
157
  | `authorize_handler` | Handler/nil | `nil` | Authorization handler |
158
158
  | `current_user_method` | Symbol/nil | `nil` | Method name for current user |
159
159
  | `user_signed_in_method` | Symbol/nil | `nil` | Method name for signed-in check |
160
+ | `open_access` | Boolean | `false` | Opt out of the fail-closed access guard (demo/non-production only); ignored when a handler is configured |
160
161
 
161
162
  Has `reset!` method.
162
163
 
@@ -66,10 +66,17 @@ config.http.retry_max = 3
66
66
  ```
67
67
 
68
68
  ### Authentication (Devise)
69
+ SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
70
+ handler configured, every engine route returns `403 Forbidden`. Configure a handler
71
+ to protect the dashboard (the handler decides access and bypasses the fail-closed guard):
69
72
  ```ruby
70
73
  config.authentication.authenticate_with :authenticate_user!
71
74
  config.authentication.authorize_with ->(c) { c.current_user&.admin? }
72
75
  ```
76
+ For local demos/sandboxes only, opt out of the fail-closed guard (non-production):
77
+ ```ruby
78
+ config.authentication.open_access = true # default: false -- demo/non-production only
79
+ ```
73
80
 
74
81
  ### Image Downloads (Active Storage)
75
82
  ```ruby
@@ -167,7 +174,7 @@ end
167
174
  - [ ] Initializer exists at `config/initializers/source_monitor.rb`
168
175
  - [ ] Queue names match `config/queue.yml` (or `config/solid_queue.yml`) entries
169
176
  - [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by install generator)
170
- - [ ] Authentication hooks configured for host auth system
177
+ - [ ] Authentication decision made: configure `authenticate_with`/`authorize_with` (fail-closed default) OR set `config.authentication.open_access = true` for demos only
171
178
  - [ ] HTTP timeouts appropriate for target feeds
172
179
  - [ ] Retention policy set for production
173
180
  - [ ] Workers restarted after configuration changes
@@ -283,10 +283,17 @@ config.realtime.solid_cable.connects_to = { database: { writing: :cable } }
283
283
 
284
284
  Class: `SourceMonitor::Configuration::AuthenticationSettings`
285
285
 
286
+ **Fail-closed by default.** When no `authenticate_with`/`authorize_with` handler is
287
+ configured and `open_access` is `false`, the engine denies every route with
288
+ `403 Forbidden` (see `SourceMonitor::Security::Authentication.access_denied_by_default?`).
289
+ Configuring either handler makes the handler authoritative and bypasses the
290
+ fail-closed guard.
291
+
286
292
  | Setting | Type | Default | Description |
287
293
  |---|---|---|---|
288
294
  | `current_user_method` | Symbol/nil | `nil` | Controller method to get current user |
289
295
  | `user_signed_in_method` | Symbol/nil | `nil` | Controller method to check sign-in status |
296
+ | `open_access` | Boolean | `false` | Opt out of the fail-closed guard so engine routes are public. **Demo/non-production only.** Ignored when a handler is configured. |
290
297
 
291
298
  ### Methods
292
299
 
@@ -312,6 +319,10 @@ config.authentication.authorize_with ->(controller) {
312
319
  config.authentication.authorize_with do
313
320
  redirect_to root_path unless current_user&.admin?
314
321
  end
322
+
323
+ # Open access (demo/non-production only) -- disables the fail-closed guard.
324
+ # Leave commented/false in production. A configured handler always wins.
325
+ config.authentication.open_access = true # default: false
315
326
  ```
316
327
 
317
328
  ---
@@ -119,7 +119,7 @@ After installation, review and customize the initializer. Key areas:
119
119
  | Section | Purpose |
120
120
  |---|---|
121
121
  | Queue settings | Queue names, concurrency, namespace |
122
- | Authentication | `authenticate_with`, `authorize_with` hooks |
122
+ | Authentication | **Fail-closed by default** -- configure `authenticate_with`/`authorize_with` hooks, or set `open_access = true` for demos only |
123
123
  | HTTP client | Timeouts, proxy, retries |
124
124
  | Fetching | Adaptive scheduling intervals and factors |
125
125
  | Health | Auto-pause/resume thresholds |
@@ -152,7 +152,17 @@ SOURCE_MONITOR_SETUP_TELEMETRY=true bin/source_monitor verify
152
152
  # Logs to log/source_monitor_setup.log
153
153
  ```
154
154
 
155
- ## Devise Integration
155
+ ## Authentication (Fail-Closed by Default)
156
+
157
+ SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
158
+ handler configured, every engine route (including create/update/delete/enqueue actions)
159
+ returns `403 Forbidden`. You must make one of two choices during setup:
160
+
161
+ 1. **Configure a handler** (recommended) -- the handler decides access and bypasses the
162
+ fail-closed guard.
163
+ 2. **Set `config.authentication.open_access = true`** -- opts out of the guard so routes
164
+ are public. **Demo/non-production only.** A configured handler always takes precedence
165
+ over this flag.
156
166
 
157
167
  When Devise is detected, the guided installer offers to wire authentication hooks:
158
168
 
@@ -238,6 +248,6 @@ end
238
248
  - [x] `Procfile.dev` includes `jobs:` entry for Solid Queue (handled by generator)
239
249
  - [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by generator)
240
250
  - [ ] Solid Queue workers started
241
- - [ ] Authentication hooks configured in initializer
251
+ - [ ] Authentication decision made (engine is fail-closed by default): handler configured via `authenticate_with`/`authorize_with`, OR `config.authentication.open_access = true` for demos only
242
252
  - [ ] `bin/source_monitor verify` passes
243
253
  - [ ] Dashboard accessible at mount path
@@ -57,7 +57,12 @@ SourceMonitor.configure do |config|
57
57
  # ===========================================================================
58
58
  # Authentication
59
59
  # ===========================================================================
60
+ # SECURITY: SourceMonitor is FAIL-CLOSED by default. If you do not configure
61
+ # an authentication or authorization handler below, every engine route
62
+ # (including create/update/delete/enqueue actions) returns 403 Forbidden.
60
63
  # Handlers: Symbol (invoked on controller) or callable (receives controller).
64
+ # As soon as a handler is configured it decides access and the fail-closed
65
+ # guard no longer applies.
61
66
 
62
67
  # Authenticate before accessing any SourceMonitor page.
63
68
  # config.authentication.authenticate_with :authenticate_user!
@@ -71,6 +76,12 @@ SourceMonitor.configure do |config|
71
76
  # config.authentication.current_user_method = :current_user
72
77
  # config.authentication.user_signed_in_method = :user_signed_in?
73
78
 
79
+ # Explicit opt-in for open/unauthenticated access. Leave commented out in
80
+ # production. Only enable for local demos or sandboxes where engine routes
81
+ # are deliberately public.
82
+ # WARNING: non-production / demo only -- this disables the fail-closed guard.
83
+ # config.authentication.open_access = true
84
+
74
85
  # ===========================================================================
75
86
  # HTTP Client
76
87
  # ===========================================================================
@@ -57,6 +57,10 @@ bin/rails db:migrate
57
57
 
58
58
  ## Phase 5: Configure Authentication
59
59
 
60
+ **Fail-closed by default.** With no handler configured, every engine route returns
61
+ `403 Forbidden`. Make one of two choices: configure a handler (recommended) OR opt into
62
+ open access for demos only.
63
+
60
64
  Edit `config/initializers/source_monitor.rb`:
61
65
 
62
66
  ```ruby
@@ -68,10 +72,14 @@ SourceMonitor.configure do |config|
68
72
  }
69
73
  config.authentication.current_user_method = :current_user
70
74
  config.authentication.user_signed_in_method = :user_signed_in?
75
+
76
+ # OR, for local demos/sandboxes only (non-production) -- opt out of the
77
+ # fail-closed guard so routes are public. A configured handler always wins.
78
+ # config.authentication.open_access = true
71
79
  end
72
80
  ```
73
81
 
74
- - [ ] Authentication hook configured
82
+ - [ ] Authentication decision made: handler configured (fail-closed default) OR `open_access = true` set for demos only
75
83
  - [ ] Authorization hook configured (if needed)
76
84
 
77
85
  ## Phase 6: Configure Workers
@@ -2,6 +2,32 @@
2
2
 
3
3
  Version-specific migration notes for each major/minor version transition. Agents should reference this file when guiding users through multi-version upgrades.
4
4
 
5
+ ## 0.14.0 to 0.15.0
6
+
7
+ **Key changes:**
8
+ - **Security:** Rails bumped to 8.1.3 (security release), picking up fixes for five CVEs including XSS in tag/DebugExceptions helpers, an Active Storage path-traversal, and a NumberConverter issue.
9
+ - **ViewComponent 4.x:** the `view_component` dependency widens from `>= 3.0, < 4.0` to `>= 3.0, < 5.0`, allowing host apps to resolve ViewComponent 4.x (engine lockfile resolves to 4.5.0). Engine components use only stable APIs unaffected by v4.
10
+ - **Solid Queue 1.4.0:** bumped from 1.3.1 with race-condition and supervisor stability fixes; dynamic recurring tasks are opt-in.
11
+ - **Documentation:** engine conventions consolidated into `AGENTS.md`; `CLAUDE.md` now points to it.
12
+
13
+ **Action items:**
14
+ 1. `bundle update source_monitor`
15
+ 2. `bin/rails source_monitor:upgrade`
16
+ 3. No migrations or breaking config/API changes.
17
+ 4. If the host app has ViewComponent 3.x customizations (custom components, previews), test after upgrading and consult ViewComponent v4 migration guides if needed.
18
+
19
+ ## 0.13.1 to 0.14.0
20
+
21
+ **Key changes:**
22
+ - **BREAKING — Fail-closed by default** (#129): engine routes return `403 Forbidden` unless `config.authentication.authenticate_with` or `config.authentication.authorize_with` is configured, OR `config.authentication.open_access = true` is explicitly set (non-production only).
23
+ - **Request flashes response-local** (#130): flash toasts no longer broadcast to the global `source_monitor_notifications` ActionCable stream; rendered inline on HTML loads and appended on turbo_stream responses instead. Background operational toasts stay global but context-free.
24
+ - **Gem package excludes `.claude` internals** (#131): only `.claude/skills/sm-*` shipped; agents, hooks, `settings.json`, commands, and non-`sm-*` skills excluded.
25
+
26
+ **Action items:**
27
+ 1. `bundle update source_monitor`
28
+ 2. **Action required (auth):** in `config/initializers/source_monitor.rb`, ensure at least one of `authenticate_with`, `authorize_with`, or `open_access = true` (demo/sandbox only). Without one, all engine routes return `403`.
29
+ 3. No migrations or other breaking API changes; flash/packaging changes apply transparently.
30
+
5
31
  ## 0.12.4 to 0.13.0
6
32
 
7
33
  **Key changes:**
data/AGENTS.md CHANGED
@@ -1,47 +1,74 @@
1
1
  # Repository Guidelines
2
2
 
3
- Refer to `CLAUDE.md` for project conventions and skills catalog.
3
+ This file is the **canonical engine convention reference, shared across all AI coding agents** (Claude Code, Codex, etc.). It is the single source of truth for tech stack, architecture, testing, quality gates, CI, security, and commands.
4
+
5
+ `CLAUDE.md` holds only Claude Code-specific context (working-memory header, VBW commands, and the `.claude/` agent/skill catalogs) and references this file for conventions. When a convention changes, edit it **here**, not in `CLAUDE.md`.
4
6
 
5
7
  Use rbenv for all ruby and bundler/gem commands, not the system ruby.
6
8
 
7
- ## Contribution Workflow
9
+ ## Tech Stack
8
10
 
9
- - Treat the engine like any external contributor would: no direct commits to `main`.
10
- - Before writing code, branch off the latest `origin/main` (use a descriptive `feature/` or `bugfix/` prefix) and open a draft PR on `github.com/dchuk/source_monitor`.
11
- - Push early and often to that branch so history stays visible; keep commits scoped and rebases local to your branch only.
12
- - Move the PR out of draft once tests pass and the slice is ready for review; request at least one review and wait for all CI jobs (lint, security, Rails tests + diff coverage) to succeed before merging.
13
- - The `test` job enforces diff coverage via `bin/check-diff-coverage`; if legitimate gaps remain after new code paths, refresh `config/coverage_baseline.json` by running `bin/test-coverage` followed by `bin/update-coverage-baseline` on the updated branch and commit the regenerated baseline.
14
- - Merge via the PR UI (squash or rebase as agreed) after approval; avoid rewriting shared history post-push.
15
- - Tag releases only after the release PR merges, then follow the checklist in `CHANGELOG.md`.
11
+ | Layer | Technology |
12
+ |-------|------------|
13
+ | Ruby | 4.0+ |
14
+ | Rails | 8.x |
15
+ | Testing | Minitest (no fixtures -- uses factory helpers + WebMock/VCR) |
16
+ | Authorization | Host app responsibility (mountable engine); **fail-closed by default** (#129) |
17
+ | Jobs | Solid Queue |
18
+ | Frontend | Hotwire (Turbo + Stimulus) + Tailwind CSS |
19
+ | Linting | RuboCop (omakase) + Brakeman |
20
+ | Database | PostgreSQL only |
16
21
 
17
- ## Library Documentation
22
+ ## Project Structure & Module Organization
18
23
 
19
- - Use Context7 MCP constantly to look up fresh documentation for any task you're looking to complete, especially tasks that rely on using libraries or gems
24
+ The engine follows the Rails mountable layout generated by `rails plugin new source_monitor --mountable`. Runtime code lives under `app/` (controllers, jobs, views) and is namespaced as `SourceMonitor`. Long-lived services, adapters, and instrumentation helpers belong in `lib/source_monitor/`. Generator templates and install scripts reside in `lib/generators/source_monitor/`. Tests sit in `test/`, with fixtures under `test/fixtures/feeds/`, and the dummy host app in `test/dummy/` for integration coverage. Keep shared UI assets under `app/assets/` and engine configuration in `config/initializers/`.
20
25
 
21
- ### Project Dependencies & context7 links
26
+ ## Architecture Conventions
22
27
 
23
- - Feedjirra - https://context7.com/feedjira/feedjira
28
+ ### Models First
29
+ - Business logic lives in models. Use concerns for horizontal sharing.
30
+ - Service objects ONLY for operations spanning 3+ models or external integrations.
31
+ - Query objects for complex queries that don't fit a single scope.
32
+ - Presenters (SimpleDelegator) for view-specific formatting.
24
33
 
25
- ## Project Structure & Module Organization
34
+ ### Everything-is-CRUD Routing
35
+ - Prefer creating a new resource over adding custom actions.
36
+ - `POST /posts/:id/publications` over `POST /posts/:id/publish`.
37
+ - RESTful routes only; no `member` or `collection` blocks with custom verbs.
26
38
 
27
- The engine follows the Rails mountable layout generated by `rails plugin new source_monitor --mountable`. Runtime code lives under `app/` (controllers, jobs, views) and is namespaced as `SourceMonitor`. Long-lived services, adapters, and instrumentation helpers belong in `lib/source_monitor/`. Generator templates and install scripts reside in `lib/generators/source_monitor/`. Tests sit in `test/`, with fixtures under `test/fixtures/feeds/`, and the dummy host app in `test/dummy/` for integration coverage. Keep shared UI assets under `app/assets/` and engine configuration in `config/initializers/`.
39
+ ### State as Records
40
+ - Track business state transitions as separate records (who/when/why).
41
+ - Boolean columns ONLY for technical flags (e.g., `email_verified`, `open_access`).
28
42
 
29
- ## Build, Test, and Development Commands
43
+ ### Jobs
44
+ - Shallow jobs: call `_later` or `_now` methods on models/services.
45
+ - Jobs contain only deserialization + delegation. No business logic.
46
+ - Use Solid Queue recurring jobs for scheduled work.
30
47
 
31
- Run `bin/setup` to install gems, prepare the dummy database, and compile Tailwind. Use `bin/rails test` (or `bundle exec rake test`) for the full MiniTest suite, including system tests through the dummy app. During feature work, `bin/dev` starts the dummy app with Solid Queue workers and Tailwind watcher. Trigger a feed ingest locally with `bin/rails runner 'SourceMonitor::FetchFeeds.call'` once the fetcher service lands.
48
+ ### Frontend
49
+ - Turbo Frames for partial page updates.
50
+ - Turbo Streams for real-time broadcasts.
51
+ - Stimulus controllers: small, focused, one behavior each.
52
+ - Tailwind CSS utility classes; extract components for repeated patterns.
32
53
 
33
- ## Background Job Defaults
54
+ ## Coding Style & Naming Conventions
34
55
 
35
- - SourceMonitor ensures Solid Queue is the default adapter when the host app is still using the async adapter, but respects any explicit `ActiveJob` configuration already in place. Override queues/concurrency via `SourceMonitor.configure`.
36
- - Queue names are namespaced (`source_monitor_fetch`/`source_monitor_scrape` by default) and automatically honor host `queue_name_prefix`. Use `SourceMonitor.queue_name(:fetch)` helpers inside jobs.
37
- - Dashboard queue metrics read directly from Solid Queue tables via `SourceMonitor::Jobs::SolidQueueMetrics`. Host apps must install the Solid Queue migrations (reuse the engine's `20251009140000_create_solid_queue_tables.rb` or run `rails solid_queue:install`) for the card to surface ready/scheduled/failed counts; otherwise the UI falls back to an availability warning. Mission Control remains optional for deeper drill-downs.
38
- - The dummy host keeps Solid Queue tables in the primary database via `20251009140000_create_solid_queue_tables.rb`. Real apps can either reuse that migration or run `rails solid_queue:install` to manage a dedicated queue database—Mission Control expects one of those setups before it can surface data.
39
- - Recurring schedules live in `config/recurring.yml`, scheduling `SourceMonitor::ScheduleFetchesJob` each minute plus the scraping scheduler every two minutes. Override the schedule path with `bin/jobs --recurring_schedule_file=...` (or `SOLID_QUEUE_RECURRING_SCHEDULE_FILE`) and disable recurring runners with `SOLID_QUEUE_SKIP_RECURRING=true` or `bin/jobs --skip-recurring`.
40
- - Hosts that need to wrap Solid Queue command execution can set `config.recurring_command_job_class` in the generated initializer to point at their custom job class.
56
+ Use two-space indentation and Ruby 4.0+ syntax. Keep engine classes under the `SourceMonitor::` namespace; new modules should mirror their directory, e.g., `lib/source_monitor/fetching/pipeline.rb`. Favor service objects ending in `Service`, jobs ending in `Job`, and background channels ending in `Channel`. Rails defaults handle formatting, but run `bin/rubocop` (configured via `.rubocop.yml`) before opening a PR. For views, stick with ERB and Tailwind utility classes.
57
+
58
+ Sub-module extraction pattern: create `module/submodule.rb` with `require_relative`, lazy accessors, and forwarding methods for backward compatibility. The project uses Ruby autoload for `lib/` modules (not Zeitwerk).
59
+
60
+ ## Clean Coding Principles
61
+
62
+ - **SRP**: Classes and methods should have a single responsibility.
63
+ - **DRY**: Avoid duplication; changes should only need one edit.
64
+ - **Depend on behaviour, not data**: Wrap instance variables in methods (`attr_reader`); use Struct for data structures.
65
+ - **Minimise dependencies**: Use dependency injection, encapsulate external messages, prefer hash arguments.
66
+ - **Depend on things that change less often than you do.**
41
67
 
42
68
  ## Configuration DSL
43
69
 
44
- - `SourceMonitor.configure` now exposes structured namespaces:
70
+ - `SourceMonitor.configure` exposes structured namespaces:
71
+ - `config.authentication` gates the engine. With no `authenticate_with`/`authorize_with` handler configured, the engine is **fail-closed** and returns `403`. Set `config.authentication.open_access = true` (default `false`) only for local demos/sandboxes — never production.
45
72
  - `config.http` for Faraday timeouts, retry policy, proxy, and default headers. Per the [Faraday retry docs](https://github.com/lostisland/faraday/blob/main/docs/middleware/index.md), middleware options map 1:1 to the settings we surface (max retries, interval, backoff, statuses).
46
73
  - `config.scrapers` registers/overrides adapters by name; adapters must inherit from `SourceMonitor::Scrapers::Base` and are discovered before constant lookup.
47
74
  - `config.retention` supplies global defaults for `items_retention_days`, `max_items`, and the pruning strategy (`:destroy` or `:soft_delete`). Runtimes treat blank source fields as “inherit from config”.
@@ -57,29 +84,109 @@ Run `bin/setup` to install gems, prepare the dummy database, and compile Tailwin
57
84
  - `SourceMonitor::ItemCleanupJob` batches retention pruning across sources and can soft delete records (`rake source_monitor:cleanup:items` honours `SOFT_DELETE=true`, `SOURCE_IDS=1,2`). `SourceMonitor::LogCleanupJob` prunes old fetch/scrape logs (`rake source_monitor:cleanup:logs`, override `FETCH_LOG_DAYS` / `SCRAPE_LOG_DAYS`).
58
85
  - Nightly recurring entries in `config/recurring.yml` enqueue both cleanup jobs by default; adjust schedules or disable via Solid Queue overrides as needed.
59
86
 
60
- ## Coding Style & Naming Conventions
87
+ ## Background Job Defaults
61
88
 
62
- Use two-space indentation and Ruby 4.0+ syntax. Keep engine classes under the `SourceMonitor::` namespace; new modules should mirror their directory, e.g., `lib/source_monitor/fetching/pipeline.rb`. Favor service objects ending in `Service`, jobs ending in `Job`, and background channels ending in `Channel`. Rails defaults handle formatting, but run `bundle exec rubocop` (configured via `.rubocop.yml`) before opening a PR. For views, stick with ERB and Tailwind utility classes.
89
+ - SourceMonitor ensures Solid Queue is the default adapter when the host app is still using the async adapter, but respects any explicit `ActiveJob` configuration already in place. Override queues/concurrency via `SourceMonitor.configure`.
90
+ - Queue names are namespaced (`source_monitor_fetch`/`source_monitor_scrape` by default) and automatically honor host `queue_name_prefix`. Use `SourceMonitor.queue_name(:fetch)` helpers inside jobs.
91
+ - Dashboard queue metrics read directly from Solid Queue tables via `SourceMonitor::Jobs::SolidQueueMetrics`. Host apps must install the Solid Queue migrations (reuse the engine's `20251009140000_create_solid_queue_tables.rb` or run `rails solid_queue:install`) for the card to surface ready/scheduled/failed counts; otherwise the UI falls back to an availability warning. Mission Control remains optional for deeper drill-downs.
92
+ - The dummy host keeps Solid Queue tables in the primary database via `20251009140000_create_solid_queue_tables.rb`. Real apps can either reuse that migration or run `rails solid_queue:install` to manage a dedicated queue database—Mission Control expects one of those setups before it can surface data.
93
+ - Recurring schedules live in `config/recurring.yml`, scheduling `SourceMonitor::ScheduleFetchesJob` each minute plus the scraping scheduler every two minutes. Override the schedule path with `bin/jobs --recurring_schedule_file=...` (or `SOLID_QUEUE_RECURRING_SCHEDULE_FILE`) and disable recurring runners with `SOLID_QUEUE_SKIP_RECURRING=true` or `bin/jobs --skip-recurring`.
94
+ - Hosts that need to wrap Solid Queue command execution can set `config.recurring_command_job_class` in the generated initializer to point at their custom job class.
95
+
96
+ ## Build, Test, and Development Commands
97
+
98
+ Run `bin/setup` to install gems, prepare the dummy database, and compile Tailwind. During feature work, `bin/dev` starts the dummy app with Solid Queue workers and Tailwind watcher.
99
+
100
+ ```bash
101
+ bin/dev # Start dev server
102
+ bin/rails test # Run the MiniTest suite (NOT a substitute for CI -- see below)
103
+ bin/rubocop # Check style
104
+ bin/rubocop -a # Auto-fix style
105
+ bin/brakeman --no-pager # Security scan
106
+ bin/rails db:migrate # Run migrations
107
+ ```
108
+
109
+ The dummy host app runs on **port 3002** (`cd test/dummy && bin/rails server -p 3002`).
110
+
111
+ ## Testing
63
112
 
64
- ## Testing Guidelines
113
+ - **Framework:** Minitest. NEVER use RSpec or FactoryBot. Name files with `_test.rb` and wrap suites in `module SourceMonitor`. Tests live in `test/models`, `test/controllers`, `test/system`, `test/lib`, `test/integration`.
114
+ - **Helpers:** `create_source!` factory, `with_inline_jobs`, `with_queue_adapter`.
115
+ - **HTTP:** WebMock disables external HTTP; VCR for recorded cassettes under `test/vcr_cassettes/` (record new fixtures with descriptive names like `source_fetch_success.yml`).
116
+ - **Config isolation:** `test/test_helper.rb` calls `SourceMonitor.reset_configuration!` in every test's setup (rebuilding a fresh `Configuration`), so any config a test relies on must be set in setup, not the dummy initializer.
117
+ - **Coverage:** Target >90% for new services; cover every model validation, scope, public method, and controller action, plus regression tests for bug fixes.
118
+ - **Parallelism:** Coverage runs need `COVERAGE=1 PARALLEL_WORKERS=1` with **threads** (not forks) to avoid a PG segfault and SimpleCov data loss. Scope queries to a specific source/item to prevent cross-test contamination in parallel runs.
119
+ - **Automate what you can:** Anything verifiable programmatically (config defaults, job enqueue behavior, controller responses) should be a test, not a manual checkpoint.
120
+
121
+ ## Quality Gates
122
+
123
+ - `bin/rubocop` — zero offenses before commit (omakase: only ~45/775 cops enabled, all Metrics cops disabled — no file-size enforcement).
124
+ - `bin/brakeman --no-pager` — zero warnings before merge.
125
+ - `bin/rails test` — all tests pass.
126
+ - `yarn build` — rebuild JS assets if any `.js` files changed (ESLint runs in CI).
127
+ - No N+1 queries (use `includes`/`preload`).
128
+ - No hardcoded credentials (use Rails credentials or ENV).
129
+
130
+ ### Pre-Push CI Checklist (run ALL before pushing to GitHub)
131
+
132
+ Before pushing any branch (especially release branches), run the full CI equivalent locally:
133
+
134
+ 1. `bin/rubocop` — catches Ruby lint issues.
135
+ 2. `bin/test-coverage` — **this is what CI's `test` job actually runs.** Do NOT rely on `bin/rails test` to predict CI: `bin/rails test` uses a different harness/seed and does NOT run the diff-coverage gate, so it can be green (e.g. 1741/0) while CI fails. `bin/test-coverage` runs the real seed, the second `health_suite` pass, and the host-app-template test that **changes the process CWD mid-suite** (see gemspec note below).
136
+ 3. `bundle exec ruby bin/check-diff-coverage` — run AFTER `bin/test-coverage` (it reads `coverage/.resultset.json`). Reproduces the CI diff-coverage gate locally (threshold 90% on changed `app/`/`lib/` lines vs `origin/main`). This is the only way to know the gate passes before pushing.
137
+ 4. `bin/brakeman --no-pager` — catches security issues.
138
+ 5. `yarn build` — rebuilds JS and catches ESLint issues (CI runs ESLint separately).
139
+
140
+ If legitimate coverage gaps remain after new code paths, refresh the baseline: `bin/test-coverage` then `bin/update-coverage-baseline`, and commit the regenerated `config/coverage_baseline.json`.
141
+
142
+ **Why:** CI failures cost ~5 min per round-trip. Hard-won lessons:
143
+ - **CI runs `bin/test-coverage`, not `bin/rails test`** — replicate the gate locally with steps 2 + 3. (v0.14.0: a green local `bin/rails test` masked a CI `bin/test-coverage` failure.)
144
+ - **Diff coverage covers EVERY changed `app/`/`lib/` line**, including defensive/edge branches with no natural caller. If a branch can't be reached through a normal engine route (e.g. the `#130` turbo_stream flash-append, which only fires when a turbo_stream request carries a Rails flash), add a **test-only probe controller in the dummy app** (pattern: `test/dummy/app/controllers/test_support_controller.rb` + a route in `test/dummy/config/routes.rb`); subclass `SourceMonitor::ApplicationController` if the branch lives in the engine's filter chain.
145
+ - Every `rescue`/fallback/error path in new source code needs test coverage.
146
+ - **Gemspec packaging must be CWD-independent.** Do NOT use a bare `Dir[...]` glob in `source_monitor.gemspec` for file selection — it resolves against the process CWD, and during `bin/test-coverage` a sibling test chdir's into a generated host app, so the glob returns nothing and files silently drop from the package (v0.14.0 `#131`). Drive packaging from `git ls-files` inside the existing `Dir.chdir(File.expand_path(__dir__))` block.
147
+ - JS files need `/* global */` declarations for browser APIs (MutationObserver, requestAnimationFrame, etc.); ESLint `no-undef` rejects them otherwise. Run `yarn build` after JS changes to sync sourcemaps.
148
+
149
+ ## Contribution Workflow
65
150
 
66
- MiniTest drives coverage (`test/models`, `test/controllers`, `test/system`). Name files with `_test.rb` and wrap suites in `module SourceMonitor`. Add VCR cassettes under `test/vcr_cassettes/` when touching HTTP clients, and record new fixtures with descriptive names like `source_fetch_success.yml`. Target >90% coverage for new services and include regression tests for bug fixes. Use `bin/rails test test/system` before changing UI flows.
151
+ - Treat the engine like any external contributor would: no direct commits to `main`.
152
+ - Before writing code, branch off the latest `origin/main` (use a descriptive `feature/` or `bugfix/` prefix) and open a draft PR on `github.com/dchuk/source_monitor`.
153
+ - Push early and often to that branch so history stays visible; keep commits scoped and rebases local to your branch only.
154
+ - Move the PR out of draft once tests pass and the slice is ready for review; request at least one review and wait for all CI jobs (lint, security, test + diff coverage, release_verification) to succeed before merging.
155
+ - Merge via the PR UI (squash or rebase as agreed) after approval; avoid rewriting shared history post-push.
156
+ - Tag releases only after the release PR merges, then follow the checklist in `CHANGELOG.md`. There are TWO version files — `lib/source_monitor/version.rb` AND the top-level `VERSION` — and both must match; run `bundle install` after a bump so `Gemfile.lock` stays in sync (CI runs `--frozen`).
67
157
 
68
158
  ## Commit & Pull Request Guidelines
69
159
 
70
- Adopt imperative commit messages in the format `scope: action`, e.g., `sources: enforce URL normalization`. Group unrelated work into separate commits. PRs should describe context, summarise the slice delivered, and list validation steps (`bin/rails test`, manual fetch run). Include screenshots or console output when altering UI or background jobs. Request at least one review and ensure CI completes before merge.
160
+ Use Conventional Commit subjects in the format `type(scope): description`, e.g., `fix(fetch): resolve advisory lock contention`. Group unrelated work into separate commits. PRs should describe context, summarise the slice delivered, and list validation steps (`bin/test-coverage`, manual fetch run). Include screenshots or console output when altering UI or background jobs. Request at least one review and ensure CI completes before merge.
161
+
162
+ ## Security
71
163
 
72
- ## Security & Configuration Tips
164
+ ### Protected files (NEVER read or output)
165
+ - `.env`, `.env.*`
166
+ - `config/master.key`, `config/credentials.yml.enc`
167
+ - `.kamal/secrets`
168
+ - Any `*.pem` / `*.key` files
73
169
 
170
+ ### Forbidden operations
171
+ - `git push --force` to main/master/production
172
+ - `git reset --hard` without explicit user confirmation
173
+ - `rm -rf` on root, home, or parent directories
174
+ - `chmod 777`
175
+
176
+ ### General
74
177
  Store secrets (API keys, webhook tokens) in `config/credentials/` and never commit plain-text values. When adding HTTP endpoints or webhooks, default to Solid Queue middleware for retries and respect the allowlist in `config/source_monitor.yml`. Document new environment variables in `config/application.yml.sample` and call out any migrations that impact host apps.
75
178
 
76
- ## Clean Coding Principles
179
+ ## Maintenance: Skills & Docs Alignment
77
180
 
78
- - **SRP**: Classes and methods should have a single responsibility.
79
- - **DRY**: Avoid duplication; changes should only need one edit.
80
- - **Depend on behaviour, not data**: Wrap instance variables in methods (`attr_reader`); use Struct for data structures.
81
- - **Minimise dependencies**: Use dependency injection, encapsulate external messages, prefer hash arguments.
82
- - **Depend on things that change less often than you do.**
181
+ Whenever engine code changes (models, configuration, pipeline, jobs, migrations, scrapers, events, health rules, or dashboard), the corresponding `sm-*` skill and its `reference/` files MUST be updated **in the same PR** so skills always reflect current engine behavior. Releases must also audit `README.md`, `docs/` (especially `docs/upgrade.md`), and the install initializer template against the source code.
182
+
183
+ ## Library Documentation
184
+
185
+ - Use Context7 MCP constantly to look up fresh documentation for any task, especially tasks that rely on libraries or gems.
186
+
187
+ ### Project Dependencies & context7 links
188
+
189
+ - Feedjira - https://context7.com/feedjira/feedjira
83
190
 
84
191
  ## Claude Code Skills
85
192
 
@@ -92,4 +199,4 @@ bin/rails source_monitor:skills:all # All skills
92
199
  bin/rails source_monitor:skills:remove # Remove all sm-* skills
93
200
  ```
94
201
 
95
- See `CLAUDE.md` for the full skills catalog and usage details.
202
+ See `CLAUDE.md` for the full skills catalog (consumer vs. contributor) and the `.claude/` agent catalog.
data/CHANGELOG.md CHANGED
@@ -13,6 +13,35 @@ All notable changes to this project are documented below. The format follows [Ke
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
+ - No unreleased changes yet.
17
+
18
+ ## [0.15.0] - 2026-06-18
19
+
20
+ ### Security
21
+ - Bump Rails to 8.1.3 via the 8.1.2.1 security release (#104), picking up fixes for five CVEs including XSS in tag/DebugExceptions helpers, an Active Storage path-traversal, and a NumberConverter issue.
22
+
23
+ ### Changed
24
+ - **Allow ViewComponent 4.x** (#96). The `view_component` dependency constraint widens from `>= 3.0, < 4.0` to `>= 3.0, < 5.0`, so host apps can now resolve ViewComponent 4.x (the engine's lockfile moves to 4.5.0). The engine's components use only stable ViewComponent APIs and are unaffected by the v4 upgrade — but a host app on its own ViewComponent 3.x customizations should review the v4 upgrade notes before running `bundle update`.
25
+ - Bump Solid Queue to 1.4.0 (#102) — race-condition and supervisor stability fixes; the new dynamic recurring-tasks feature is opt-in and off by default.
26
+ - Bump nokolexbor to 0.6.4 (#103) and json to 2.19.2 (#99).
27
+ - Development/CI dependency bumps: test-prof 1.6.0 (#101), webmock 3.26.2 (#100), brakeman 8.0.4 (#88), selenium-webdriver 4.41.0 (#78), stackprof 0.2.28 (#71), and the GitHub Actions artifact actions (#87, #86).
28
+
29
+ ### Documentation
30
+ - Consolidate engine conventions into `AGENTS.md` as the canonical, cross-agent reference; `CLAUDE.md` now points to it instead of duplicating content (#134).
31
+
32
+ ## [0.14.0] - 2026-05-28
33
+
34
+ ### Security (BREAKING)
35
+ - **Fail-closed engine access by default** (#129). When the host app has configured no authentication/authorization handler, every mounted SourceMonitor route now returns `403 Forbidden` instead of being publicly accessible. Previously `authenticate!`/`authorize!` were silent no-ops when unconfigured, so create/update/delete/enqueue routes were public by omission.
36
+ - **Migration:** Configure a handler — `config.authentication.authenticate_with` and/or `config.authentication.authorize_with` — to gate the engine with your host auth stack. Configured handlers are honored exactly as before.
37
+ - **Demo opt-in:** To intentionally keep routes public (local demos/sandboxes only), set `config.authentication.open_access = true` (default `false`). Do not enable this in production.
38
+
39
+ ### Fixed
40
+ - **Request flashes no longer leak across users** (#130). Request flash toasts were broadcast to a global ActionCable stream (`source_monitor_notifications`) that every connected browser tab subscribed to, so one user's flash could appear on every user's screen. Flashes are now delivered response-local — rendered inline on full-page HTML loads and appended to the turbo_stream response otherwise — reaching only the requesting tab. Background operational toasts (fetch/scrape completion) remain global but carry no per-user request context.
41
+
42
+ ### Changed
43
+ - **Gem package no longer ships `.claude` internals** (#131). The packaged gem now excludes `.claude` agents, hooks, agent-memory, `settings.json`, commands, and non-`sm-*` skills; only the intended `.claude/skills/sm-*` SourceMonitor skills are shipped. Packaging is also now CWD-independent so the skill files are included deterministically.
44
+
16
45
  ## [0.13.1] - 2026-05-28
17
46
 
18
47
  ### Fixed