.vbw-planning/milestones/polish-and-reliability/phases/01-backend-fixes/01-RESEARCH.md
lib/source_monitor/http.rb — DEFAULT_USER_AGENT, default_headers, client, configure_requestlib/source_monitor/configuration/http_settings.rb — HTTPSettings with user_agent, headers accessorslib/source_monitor/fetching/feed_fetcher.rb — request_headers (per-source custom_headers merge)test/lib/source_monitor/http_test.rb — header merge/override testsDEFAULT_USER_AGENT = "SourceMonitor/#{SourceMonitor::VERSION}" (line 17 of http.rb)default_headers builds: User-Agent, Accept (RSS-only), Accept-Encoding (line 89-97)configure_request merges: default_headers(settings).merge(headers) (line 60) — passed headers override defaultsFeedFetcher#request_headers starts from source.custom_headers, adds If-None-Match/If-Modified-SinceHTTPSettings#default_user_agent returns same "SourceMonitor/#{VERSION}" stringuser_agent accessor supports callables (lambda/proc) via resolve_callableHTTPSettings#default_user_agent to return polite bot string: "Mozilla/5.0 (compatible; SourceMonitor/#{VERSION})"Accept-Language: en-US,en;q=0.9 and DNT: 1 to default_headersReferer header in FeedFetcher#request_headers using source.website_urldefault_headers Accept to prepend text/html: "text/html, application/rss+xml, ..."app/jobs/source_monitor/source_health_check_job.rb — perform, broadcast_outcomelib/source_monitor/health/source_health_check.rb — call, create_log, successful_status?lib/source_monitor/health/source_health_monitor.rb — call, determine_status, improving_streak?lib/source_monitor/health.rb — setup!, register_fetch_callback (after_fetch_completed wiring)app/jobs/source_monitor/fetch_feed_job.rb — perform(source_id, force: true) to enqueue full fetchSourceHealthCheckJob#perform does HTTP GET, creates HealthCheckLog, broadcasts toast — never updates health_statusSourceHealthMonitor runs via after_fetch_completed callback — computes rolling success rate from fetch_logshealth_status values: healthy, warning, critical, improving, declining, auto_paused (free string, no enum)improving_streak? requires 2+ consecutive successes with prior failure in windowFetchFeedJob.perform_later(source.id, force: true) enqueues immediate full fetch bypassing should_run?FetchFeedJob.perform_later(source.id, force: true)SourceHealthCheckJob#perform after broadcast_outcomelib/source_monitor/configuration/scraping_settings.rb — DEFAULT_MAX_IN_FLIGHT = 25, reset!lib/source_monitor/scraping/enqueuer.rb — rate_limit_exhausted? (line 108-114)lib/source_monitor/scraping/state.rb — IN_FLIGHT_STATUSES = %w[pending processing]lib/source_monitor/scraping/bulk_source_scraper.rb — loop with break on rate_limited (line 75-124)lib/source_monitor/scraping/bulk_result_presenter.rb — message builder (line 42-75)test/lib/source_monitor/scraping/bulk_source_scraper_test.rb — rate limit testDEFAULT_MAX_IN_FLIGHT = 25 — counts pending + processing items:rate_limited, bulk scraper breaks loop, presenter shows messagenil already disables the limitnormalize_numeric ensures only positive integers or nilDEFAULT_MAX_IN_FLIGHT = nil (was 25)