2248 feat network sweeper UI #2653

Merged
mfreeman451 merged 30 commits from refs/pull/2653/head into staging 2026-01-12 05:27:40 +00:00
mfreeman451 commented 2026-01-12 05:17:38 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2267
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2267
Original created: 2026-01-12T05:17:38Z
Original updated: 2026-01-12T05:27:42Z
Original head: carverauto/serviceradar:2248-feat-network-sweeper-ui
Original base: staging
Original merged: 2026-01-12T05:27:40Z by @mfreeman451

User description

IMPORTANT: Please sign the Developer Certificate of Origin

Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include
a DCO sign-off statement indicating the DCO acceptance in one commit message. Here
is an example DCO Signed-off-by line in a commit message:

Signed-off-by: J. Doe <j.doe@domain.com>

Describe your changes

Code checklist before requesting a review

  • I have signed the DCO?
  • The build completes without errors?
  • All tests are passing when running make test?

PR Type

Enhancement, Tests


Description

  • Network Sweeper UI Implementation: Comprehensive LiveView module for managing network sweep configuration with advanced criteria builder, real-time progress tracking, and three main tabs (Sweep Groups, Scanner Profiles, Active Scans)

  • Device Management Enhancements: Added bulk device selection, tag editing modal, and quick filter buttons to device listing; integrated sweep status display in device detail view

  • Sweep Jobs Domain: New Ash domain with resources for SweepProfile (admin templates), SweepGroup (user-configured jobs), SweepGroupExecution (tracking), and SweepHostResult (per-host results)

  • Device Targeting DSL: Flexible criteria matching with 14+ operators (eq, neq, in, contains, in_cidr, etc.) for precise device selection in sweep groups

  • Sweep Results Ingestion: New SweepResultsIngestor module processes sweep results, updates device inventory, manages availability status, and broadcasts PubSub events for real-time UI updates

  • Agent Configuration System: Complete infrastructure for compiling and caching agent configurations with version history, templates, and cluster-wide invalidation via NATS

  • Sweep Monitoring & Cleanup: Oban workers for detecting missed executions and managing data retention with configurable periods

  • Result Buffering: Agent gateway now buffers results during core outages with exponential backoff retry strategy

  • Observability Integration: Default rules seeded for missed sweep detection and repeated missed sweep alerts

  • Database Migrations: New tables for sweep jobs, agent configurations, device tags, and supporting indexes

  • Comprehensive Test Coverage: End-to-end tests for sweep results ingestion, integration tests for config distribution, and unit tests for target criteria matching


Diagram Walkthrough

flowchart LR
  UI["Network Sweeper UI<br/>LiveView"]
  SweepGroup["SweepGroup<br/>Resource"]
  TargetCriteria["TargetCriteria<br/>DSL"]
  SweepCompiler["SweepCompiler<br/>Agent Config"]
  ConfigCache["ConfigCache<br/>ETS"]
  ConfigServer["ConfigServer<br/>GenServer"]
  AgentPayload["Agent Payload<br/>Generation"]
  SweepResults["SweepResults<br/>Ingestor"]
  DeviceInventory["Device<br/>Inventory"]
  PubSub["PubSub<br/>Events"]
  
  UI -- "creates/manages" --> SweepGroup
  SweepGroup -- "uses" --> TargetCriteria
  SweepGroup -- "compiled by" --> SweepCompiler
  SweepCompiler -- "cached in" --> ConfigCache
  ConfigCache -- "managed by" --> ConfigServer
  ConfigServer -- "integrated into" --> AgentPayload
  AgentPayload -- "executes sweep" --> SweepResults
  SweepResults -- "updates" --> DeviceInventory
  SweepResults -- "broadcasts" --> PubSub
  PubSub -- "updates" --> UI

File Walkthrough

Relevant files
Enhancement
32 files
index.ex
Network Sweeper UI LiveView with Criteria Builder               

web-ng/lib/serviceradar_web_ng_web/live/settings/networks_live/index.ex

  • New comprehensive LiveView module for managing network sweep
    configuration with 2459 lines
  • Implements three main tabs: Sweep Groups (user-configured scan jobs),
    Scanner Profiles (admin templates), and Active Scans (real-time
    monitoring)
  • Provides advanced criteria builder for targeting rules with SRQL query
    conversion and device count preview
  • Includes real-time progress tracking for running sweeps with PubSub
    event handling and scanner performance metrics display
+2459/-0
index.ex
Bulk Device Selection and Tag Management                                 

web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex

  • Added bulk device selection with per-device checkboxes and select-all
    functionality
  • Implemented bulk tag editing modal allowing users to apply tags to
    multiple devices at once
  • Added "select all matching" feature to apply operations to all devices
    matching current filters
  • Introduced quick filter buttons for common queries (Available,
    Unavailable, Swept) with visual indicators
  • Added helper functions for SRQL query execution, device UID fetching,
    and error formatting
+530/-6 
sweep_results_ingestor.ex
Sweep results ingestion and device inventory updates         

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_results_ingestor.ex

  • New module for ingesting sweep results and updating device inventory
  • Processes sweep results in batches, creates/updates device records,
    and stores SweepHostResult entries
  • Updates device availability status and adds "sweep" to
    discovery_sources
  • Broadcasts progress and completion events via PubSub for real-time UI
    updates
  • Handles execution record creation and statistics tracking
+669/-0 
target_criteria.ex
Device targeting DSL for sweep group configuration             

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/target_criteria.ex

  • New DSL module for flexible device targeting with 14+ operators (eq,
    neq, in, contains, in_cidr, etc.)
  • Provides matching, filtering, and validation functions for device
    criteria
  • Converts criteria to Ash-compatible filter expressions
  • Supports IP CIDR matching, tag-based filtering, and numeric
    comparisons
+509/-0 
sweep_group.ex
Sweep group resource with scheduling and device targeting

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_group.ex

  • New Ash resource for user-configured sweep groups with independent
    schedules
  • Supports interval-based and cron-based scheduling
  • Implements device targeting via target_criteria DSL and static_targets
  • Includes actions for managing targets, enabling/disabling groups, and
    recording executions
  • Enforces authorization policies for admin/operator roles
+380/-0 
show.ex
Device detail view with network sweep status display         

web-ng/lib/serviceradar_web_ng_web/live/device_live/show.ex

  • Adds sweep status section to device detail view showing latest sweep
    results
  • Displays device availability, response time, open ports, and sweep
    history
  • Loads sweep results by device IP from SweepHostResult records
  • Includes UI components for status indicators, response times, and port
    listings
+220/-0 
sweep_compiler.ex
Sweep configuration compiler for agent deployment               

elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex

  • Compiler module that transforms SweepGroup and SweepProfile resources
    into agent-consumable config
  • Merges profile settings with group overrides for ports, modes, and
    concurrency
  • Compiles targets from both static targets and device criteria
  • Generates deterministic config hash for change detection
+294/-0 
sweep_group_execution.ex
Sweep execution tracking resource                                               

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_group_execution.ex

  • New Ash resource for tracking sweep group executions with status
    lifecycle
  • Records execution timing, host statistics, and agent information
  • Includes actions for starting, completing, and failing executions
  • Stores scanner performance metrics from agents
  • Calculates success rate as a computed field
+288/-0 
sweep.ex
Sweep event processor with inventory update integration   

elixir/serviceradar_core/lib/serviceradar/event_writer/processors/sweep.ex

  • Extends sweep event processor to update device inventory via
    SweepResultsIngestor
  • Processes inventory updates for both execution-tracked and non-tracked
    sweep results
  • Updates device availability status and adds "sweep" to
    discovery_sources
  • Handles graceful fallback when execution context is unavailable
+137/-1 
sweep_monitor_worker.ex
Sweep group monitoring worker for missed execution detection

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_monitor_worker.ex

  • New Oban worker that monitors sweep groups for missed executions
  • Detects overdue sweeps by comparing last_run_at against configured
    interval plus grace period
  • Publishes internal logs to logs.internal.sweep for missed sweeps
  • Supports interval parsing (15m, 1h, 1d) and skips cron-based groups
+238/-0 
grpc_server.rs
gRPC server response structure for sweep execution tracking

cmd/consumers/zen/src/grpc_server.rs

  • Adds execution_id and sweep_group_id fields to gRPC response structure
  • Enables agent to report sweep execution context back to server
+2/-0     
config_cache.ex
ETS cache for compiled agent configurations                           

elixir/serviceradar_core/lib/serviceradar/agent_config/config_cache.ex

  • Implements ETS-based cache for compiled agent configurations with TTL
    support
  • Provides cache operations: get, get_if_changed, put, invalidate, and
    clear_all
  • Includes automatic cleanup of expired entries via periodic scheduled
    task
  • Supports hash-based change detection for efficient config updates
+234/-0 
sweep_host_result.ex
Ash resource for sweep host result tracking                           

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_host_result.ex

  • Defines Ash resource for tracking per-host results from network sweep
    executions
  • Includes attributes for IP, hostname, status, response time, open
    ports, and error messages
  • Provides read actions filtered by execution, IP, and device with
    sorting and pagination
  • Implements authorization policies for admin and operator roles
+237/-0 
config_server.ex
GenServer for config compilation orchestration                     

elixir/serviceradar_core/lib/serviceradar/agent_config/config_server.ex

  • GenServer that orchestrates config compilation and caching with
    automatic cache invalidation
  • Provides get_config and get_config_if_changed APIs for retrieving
    compiled configurations
  • Implements cache miss handling with compilation and database fallback
  • Supports tenant-wide and type-specific cache invalidation
+196/-0 
config_instance.ex
Ash resource for compiled config instances                             

elixir/serviceradar_core/lib/serviceradar/agent_config/config_instance.ex

  • Ash resource representing compiled configuration instances for agents
    or partitions
  • Tracks version, content hash, delivery status, and source resource IDs
  • Provides actions for creating, updating, and querying configs with
    hash-based lookups
  • Includes relationships to templates and version history with audit
    capabilities
+222/-0 
sweep_data_cleanup_worker.ex
Oban worker for sweep data retention cleanup                         

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_data_cleanup_worker.ex

  • Oban worker that periodically deletes old sweep execution data to
    prevent table growth
  • Configurable retention periods for host results (default 7 days) and
    executions (default 30 days)
  • Processes deletions in batches to avoid locking large tables
  • Filters to only delete completed/failed executions, preserving running
    ones
+175/-0 
sweep_profile.ex
Ash resource for network scanner profiles                               

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_profile.ex

  • Ash resource for admin-managed scanner profiles defining reusable scan
    configurations
  • Includes attributes for ports, sweep modes, concurrency, timeout, and
    ICMP/TCP settings
  • Provides read actions to list available profiles and lookup by name
  • Implements authorization restricting admin-only profiles to
    administrators
+215/-0 
sweep_pubsub.ex
PubSub broadcaster for sweep execution events                       

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_pubsub.ex

  • PubSub broadcaster for sweep execution updates with tenant and
    execution-specific topics
  • Provides subscription and broadcast functions for execution lifecycle
    events (started, progress, completed, failed)
  • Includes safe broadcasting that gracefully handles unavailable PubSub
  • Broadcasts detailed progress updates with host and device statistics
+166/-0 
rule_seeder.ex
GenServer for seeding default observability rules               

elixir/serviceradar_core/lib/serviceradar/observability/rule_seeder.ex

  • GenServer that seeds default LogPromotionRules and StatefulAlertRules
    for each tenant
  • Creates rules for missed sweep detection and repeated missed sweep
    alerts out of the box
  • Checks for existing rules before seeding to avoid duplicates
  • Includes configurable delay before seeding to allow system
    initialization
+162/-0 
status_processor.ex
Add result buffering and telemetry to status processor     

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_processor.ex

  • Adds forward function with buffering support for failed result
    deliveries
  • Implements StatusBuffer integration to queue results when core is
    unavailable
  • Adds telemetry metrics for result forwarding with duration and result
    status tracking
  • Removes retry queueing logic in favor of explicit buffering strategy
+63/-11 
config_template.ex
Ash resource for configuration templates                                 

elixir/serviceradar_core/lib/serviceradar/agent_config/config_template.ex

  • Ash resource for reusable configuration templates with schema and
    default values
  • Supports tenant-specific and admin-only templates with enable/disable
    flags
  • Provides read actions filtered by config type and user role
  • Includes relationships to config instances and unique name constraints
    per tenant/type
+163/-0 
status_buffer.ex
In-memory buffer for result payloads during outages           

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_buffer.ex

  • GenServer implementing bounded in-memory queue for buffering result
    payloads during core outages
  • Configurable via environment variables for max entries and flush
    interval
  • Periodically flushes buffered results back to core with exponential
    backoff on failure
  • Drops oldest entries when buffer reaches capacity to prevent unbounded
    growth
+134/-0 
config_version.ex
Ash resource for config version history tracking                 

elixir/serviceradar_core/lib/serviceradar/agent_config/config_version.ex

  • Ash resource tracking version history of configuration instances for
    audit and rollback
  • Stores compiled config, content hash, source IDs, and actor
    information per version
  • Provides read actions to retrieve version history and specific
    versions by number
  • Includes unique constraint on config instance and version number
    combination
+158/-0 
config_publisher.ex
NATS publisher for config invalidation events                       

elixir/serviceradar_core/lib/serviceradar/agent_config/config_publisher.ex

  • Publishes config invalidation events to NATS for cluster-wide cache
    invalidation
  • Invalidates local cache immediately and publishes structured events
    with resource metadata
  • Builds NATS subjects and payloads with tenant, config type, and change
    information
  • Gracefully handles NATS connection unavailability while ensuring local
    cache is invalidated
+119/-0 
agent_config_generator.ex
Integrate sweep config into agent payload generation         

elixir/serviceradar_core/lib/serviceradar/edge/agent_config_generator.ex

  • Adds load_sweep_config function to retrieve compiled sweep
    configurations from ConfigServer
  • Integrates sweep config into the full agent payload under the sweep
    key
  • Includes error handling and logging for missing or failed sweep config
    loads
  • Updates build_config to merge sweep config with sync payload and
    recompute version hash
+35/-5   
compiler.ex
Behaviour definition for config compilers                               

elixir/serviceradar_core/lib/serviceradar/agent_config/compiler.ex

  • Defines behaviour for config compilers that transform Ash resources
    into agent-consumable JSON
  • Includes registry of available compilers and lookup functions for
    config types
  • Provides content_hash function for SHA256-based change detection
  • Documents compiler implementation requirements and source resource
    tracking
+102/-0 
create_version_history.ex
Ash change for automatic config version history                   

elixir/serviceradar_core/lib/serviceradar/agent_config/changes/create_version_history.ex

  • Ash change that automatically creates version history records when
    config instances are updated
  • Captures pre-update state including config, hash, version, and source
    IDs for audit trail
  • Extracts actor information from context for change attribution
  • Logs warnings but doesn't fail main operation if history creation
    fails
+81/-0   
template_seeder.ex
Add sweep monitoring templates and alert rules                     

elixir/serviceradar_core/lib/serviceradar/observability/template_seeder.ex

  • Adds passthrough log template for internal sweep logs with subject
    logs.internal.sweep
  • Adds promote_missed_sweeps log promotion rule to convert sweep.missed
    events to observability events
  • Adds repeated_missed_sweeps stateful alert rule to trigger alerts on
    multiple missed executions
  • Includes configuration for severity, category, and alert thresholds
+49/-0   
monitoring.pb.ex
Extend sweep completion status with scanner stats               

elixir/serviceradar_core/lib/serviceradar/proto/monitoring.pb.ex

  • Adds execution_id and sweep_group_id fields to SweepCompletionStatus
    protobuf message
  • Introduces new SweepScannerStats protobuf message with detailed
    packet, ring, retry, and port statistics
  • Includes rate limiting and packet drop metrics for network scanner
    diagnostics
  • Extends sweep completion reporting with scanner-level performance data
+22/-0   
config_invalidation_notifier.ex
Ash notifier for config cache invalidation                             

elixir/serviceradar_core/lib/serviceradar/agent_config/config_invalidation_notifier.ex

  • Ash notifier that triggers cache invalidation when ConfigInstance or
    ConfigTemplate resources change
  • Publishes NATS events for cluster-wide invalidation on create, update,
    and delete actions
  • Extracts tenant ID and config type from records for targeted
    invalidation
  • Logs invalidation events for debugging and monitoring
+70/-0   
sweep_jobs.ex
Ash domain for sweep job management                                           

elixir/serviceradar_core/lib/serviceradar/sweep_jobs.ex

  • Defines SweepJobs Ash domain for network sweep job management
  • Documents sweep group organization, device targeting DSL, and resource
    relationships
  • Registers four resources: SweepProfile, SweepGroup,
    SweepGroupExecution, SweepHostResult
  • Configures authorization and AshAdmin integration for domain
    management
+50/-0   
sync_ingestor.ex
Add tags field support to sync ingestor                                   

elixir/serviceradar_core/lib/serviceradar/inventory/sync_ingestor.ex

  • Adds tags field extraction and initialization in device update
    processing
  • Includes tags in both new device creation and update data structures
  • Initializes tags as empty map in default update structure
  • Supports tag data from sync payloads for device metadata enrichment
+3/-0     
Tests
5 files
sweep_results_flow_e2e_test.exs
Sweep Results Ingestion E2E Test                                                 

elixir/serviceradar_core/test/serviceradar/sweep_jobs/sweep_results_flow_e2e_test.exs

  • New end-to-end integration test for sweep results ingestion workflow
  • Tests device creation, updates, and availability status changes from
    sweep results
  • Validates execution statistics (hosts total, available, failed) and
    device discovery source tracking
  • Verifies host result records are properly created with correct status
    and port information
+163/-0 
sweep_compiler_test.exs
Tests for sweep compiler and target criteria validation   

elixir/serviceradar_core/test/serviceradar/sweep_jobs/sweep_compiler_test.exs

  • Comprehensive test suite for sweep config generation and validation
  • Tests config hashing, target criteria DSL operators, and
    agent-consumable format
  • Validates CIDR/IP parsing, port ranges, and sweep modes
  • Documents expected structure for Go agent consumption
+363/-0 
target_criteria_test.exs
Unit tests for sweep target criteria matching                       

elixir/serviceradar_core/test/serviceradar/sweep_jobs/target_criteria_test.exs

  • Unit tests for TargetCriteria module covering tag operators (has_any,
    has_all)
  • Tests range and numeric comparison operators (in_range, gt, gte, lt,
    lte)
  • Validates null checks and criteria validation error handling
  • Tests Ash filter conversion for complex criteria expressions
+92/-0   
sweep_config_distribution_integration_test.exs
Integration test for sweep config distribution                     

elixir/serviceradar_core/test/serviceradar/edge/sweep_config_distribution_integration_test.exs

  • Integration test verifying sweep configuration is included in agent
    payload
  • Creates test device, profile, and sweep group, then validates compiled
    config contains expected targets and settings
  • Tests end-to-end flow from resource creation through config
    compilation to agent payload generation
  • Validates config hash consistency between ConfigServer and
    AgentConfigGenerator
+103/-0 
networks_live_test.exs
Tests for network settings LiveView                                           

web-ng/test/serviceradar_web_ng_web/live/settings/networks_live_test.exs

  • Tests for networks settings LiveView covering sweep groups and scanner
    profiles
  • Validates listing of sweep groups and profiles with tab switching
  • Tests new sweep group and profile form rendering with UI elements
  • Includes helper function for admin user registration and login
+72/-0   
Configuration changes
4 files
20260111200000_add_sweep_jobs_tables.exs
Database migration for sweep jobs tables                                 

elixir/serviceradar_core/priv/repo/tenant_migrations/20260111200000_add_sweep_jobs_tables.exs

  • Creates four new tables for sweep jobs domain: sweep_profiles,
    sweep_groups, sweep_group_executions, sweep_host_results
  • Defines indexes for efficient querying by tenant, partition, agent,
    and IP
  • Establishes foreign key relationships between groups, executions, and
    results
  • Supports multi-tenancy with tenant_id partitioning
+254/-0 
20260111194010_add_agent_config_tables.exs
Database migration for agent configuration tables               

elixir/serviceradar_core/priv/repo/tenant_migrations/20260111194010_add_agent_config_tables.exs

  • Creates three new database tables for agent configuration management:
    agent_config_versions, agent_config_templates, and
    agent_config_instances
  • Adds jdm_definition field to zen_rules table for JDM rule definitions
  • Establishes foreign key relationships and unique indexes for config
    instance management
  • Provides comprehensive down migration for rollback support
+199/-0 
application.ex
Register agent config and sweep job services                         

elixir/serviceradar_core/lib/serviceradar/application.ex

  • Adds ServiceRadar.Observability.RuleSeeder to supervision tree for
    seeding default rules
  • Adds ServiceRadar.AgentConfig.ConfigCache and
    ServiceRadar.AgentConfig.ConfigServer to supervision tree
  • Adds ServiceRadar.AgentConfig and ServiceRadar.SweepJobs domains to
    Ash configuration
  • Includes conditional startup based on repo_enabled configuration
+15/-0   
config.exs
Configure agent config domains and sweep job workers         

elixir/serviceradar_core/config/config.exs

  • Adds ServiceRadar.AgentConfig and ServiceRadar.SweepJobs to Ash
    domains list
  • Adds maintenance and monitoring queues to Oban configuration
  • Registers SweepDataCleanupWorker cron job for 3 AM daily cleanup
  • Registers SweepMonitorWorker cron job for 5-minute interval monitoring
+9/-3     
Refactoring
1 files
srql_components.ex
Refactor SRQL builder to use QueryBuilderComponents           

web-ng/lib/serviceradar_web_ng_web/components/srql_components.ex

  • Imports QueryBuilderComponents module for reusable query builder UI
    components
  • Renames all srql_builder_pill component calls to query_builder_pill
    throughout the SRQL builder interface
  • Updates approximately 15 component references to use the new naming
    convention
+17/-33 
Formatting
1 files
show.ex
Format agent show live view code                                                 

web-ng/lib/serviceradar_web_ng_web/live/agent_live/show.ex

  • Reformats Task.async calls for better code readability with proper
    line breaks
  • Splits long RPC call chains across multiple lines for improved
    maintainability
  • No functional changes, purely formatting improvements
+18/-6   
Additional files
72 files
MODULE.bazel +1/-10   
server.rs +2/-0     
main.rs +2/-0     
network-sweeps.md +102/-0 
troubleshooting-guide.md +8/-0     
web-ui.md +17/-0   
application.ex +3/-7     
test.exs +3/-1     
agent_config.ex +40/-0   
compute_config_hash.ex +28/-0   
increment_version.ex +25/-0   
device.ex +8/-0     
zen_rule_seeder.ex +11/-0   
20260111210000_add_sweep_cleanup_indexes.exs +38/-0   
20260111220000_add_scanner_metrics_to_executions.exs +26/-0   
20260111231229_add_device_tags.exs +19/-0   
20260111194010.json +301/-0 
20260111194010.json +174/-0 
20260111194010.json +228/-0 
20260111231229.json +533/-0 
20260111231229.json +290/-0 
20260111231229.json +349/-0 
20260111231229.json +267/-0 
20260111231229.json +212/-0 
20260111194010.json +246/-0 
design.md +75/-0   
proposal.md +20/-0   
spec.md +30/-0   
tasks.md +34/-0   
design.md +377/-0 
proposal.md +61/-0   
spec.md +128/-0 
spec.md +39/-0   
spec.md +263/-0 
spec.md +102/-0 
tasks.md +174/-0 
design.md +31/-0   
proposal.md +14/-0   
spec.md +29/-0   
tasks.md +17/-0   
BUILD.bazel +2/-0     
push_loop.go +45/-2   
server.go +134/-485
server_test.go +42/-490
sweep_config_gateway.go +197/-0 
sweep_performance_test.go +7/-47   
sweep_service.go +94/-34 
types.go +13/-12 
metrics.go +3/-1     
sweep.go +36/-1   
interfaces.go +6/-0     
BUILD.bazel +0/-1     
aggregation_test.go +9/-12   
availability_logic_test.go +5/-8     
interfaces.go +2/-0     
sweeper.go +124/-557
sweeper_prune_test.go +2/-2     
sweeper_test.go +2/-1062
tcp_optimization_test.go +6/-29   
types.go +0/-77   
monitoring.pb.go +270/-69
monitoring.proto +33/-0   
devices.rs +71/-0   
config.exs +6/-2     
serviceradar_web_ng_web.ex +1/-0     
query_builder_components.ex +24/-0   
settings_components.ex +5/-0     
ui_components.ex +8/-2     
index.ex +1/-1     
index.ex +6/-6     
router.ex +8/-0     
catalog.ex +11/-1   

Imported from GitHub pull request. Original GitHub pull request: #2267 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2267 Original created: 2026-01-12T05:17:38Z Original updated: 2026-01-12T05:27:42Z Original head: carverauto/serviceradar:2248-feat-network-sweeper-ui Original base: staging Original merged: 2026-01-12T05:27:40Z by @mfreeman451 --- ### **User description** ## IMPORTANT: Please sign the Developer Certificate of Origin Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include a [DCO sign-off statement]( https://developercertificate.org/) indicating the DCO acceptance in one commit message. Here is an example DCO Signed-off-by line in a commit message: ``` Signed-off-by: J. Doe <j.doe@domain.com> ``` ## Describe your changes ## Issue ticket number and link ## Code checklist before requesting a review - [ ] I have signed the DCO? - [ ] The build completes without errors? - [ ] All tests are passing when running make test? ___ ### **PR Type** Enhancement, Tests ___ ### **Description** - **Network Sweeper UI Implementation**: Comprehensive LiveView module for managing network sweep configuration with advanced criteria builder, real-time progress tracking, and three main tabs (Sweep Groups, Scanner Profiles, Active Scans) - **Device Management Enhancements**: Added bulk device selection, tag editing modal, and quick filter buttons to device listing; integrated sweep status display in device detail view - **Sweep Jobs Domain**: New Ash domain with resources for `SweepProfile` (admin templates), `SweepGroup` (user-configured jobs), `SweepGroupExecution` (tracking), and `SweepHostResult` (per-host results) - **Device Targeting DSL**: Flexible criteria matching with 14+ operators (eq, neq, in, contains, in_cidr, etc.) for precise device selection in sweep groups - **Sweep Results Ingestion**: New `SweepResultsIngestor` module processes sweep results, updates device inventory, manages availability status, and broadcasts PubSub events for real-time UI updates - **Agent Configuration System**: Complete infrastructure for compiling and caching agent configurations with version history, templates, and cluster-wide invalidation via NATS - **Sweep Monitoring & Cleanup**: Oban workers for detecting missed executions and managing data retention with configurable periods - **Result Buffering**: Agent gateway now buffers results during core outages with exponential backoff retry strategy - **Observability Integration**: Default rules seeded for missed sweep detection and repeated missed sweep alerts - **Database Migrations**: New tables for sweep jobs, agent configurations, device tags, and supporting indexes - **Comprehensive Test Coverage**: End-to-end tests for sweep results ingestion, integration tests for config distribution, and unit tests for target criteria matching ___ ### Diagram Walkthrough ```mermaid flowchart LR UI["Network Sweeper UI<br/>LiveView"] SweepGroup["SweepGroup<br/>Resource"] TargetCriteria["TargetCriteria<br/>DSL"] SweepCompiler["SweepCompiler<br/>Agent Config"] ConfigCache["ConfigCache<br/>ETS"] ConfigServer["ConfigServer<br/>GenServer"] AgentPayload["Agent Payload<br/>Generation"] SweepResults["SweepResults<br/>Ingestor"] DeviceInventory["Device<br/>Inventory"] PubSub["PubSub<br/>Events"] UI -- "creates/manages" --> SweepGroup SweepGroup -- "uses" --> TargetCriteria SweepGroup -- "compiled by" --> SweepCompiler SweepCompiler -- "cached in" --> ConfigCache ConfigCache -- "managed by" --> ConfigServer ConfigServer -- "integrated into" --> AgentPayload AgentPayload -- "executes sweep" --> SweepResults SweepResults -- "updates" --> DeviceInventory SweepResults -- "broadcasts" --> PubSub PubSub -- "updates" --> UI ``` <details><summary><h3>File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>32 files</summary><table> <tr> <td> <details> <summary><strong>index.ex</strong><dd><code>Network Sweeper UI LiveView with Criteria Builder</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/lib/serviceradar_web_ng_web/live/settings/networks_live/index.ex <ul><li>New comprehensive LiveView module for managing network sweep <br>configuration with 2459 lines<br> <li> Implements three main tabs: Sweep Groups (user-configured scan jobs), <br>Scanner Profiles (admin templates), and Active Scans (real-time <br>monitoring)<br> <li> Provides advanced criteria builder for targeting rules with SRQL query <br>conversion and device count preview<br> <li> Includes real-time progress tracking for running sweeps with PubSub <br>event handling and scanner performance metrics display</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-b2127e71582033bc6dfd2d7397f56bf43c1f7c613defffc504b3d8ee1e7406c4">+2459/-0</a></td> </tr> <tr> <td> <details> <summary><strong>index.ex</strong><dd><code>Bulk Device Selection and Tag Management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex <ul><li>Added bulk device selection with per-device checkboxes and select-all <br>functionality<br> <li> Implemented bulk tag editing modal allowing users to apply tags to <br>multiple devices at once<br> <li> Added "select all matching" feature to apply operations to all devices <br>matching current filters<br> <li> Introduced quick filter buttons for common queries (Available, <br>Unavailable, Swept) with visual indicators<br> <li> Added helper functions for SRQL query execution, device UID fetching, <br>and error formatting</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7">+530/-6</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_results_ingestor.ex</strong><dd><code>Sweep results ingestion and device inventory updates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_results_ingestor.ex <ul><li>New module for ingesting sweep results and updating device inventory<br> <li> Processes sweep results in batches, creates/updates device records, <br>and stores SweepHostResult entries<br> <li> Updates device availability status and adds "sweep" to <br>discovery_sources<br> <li> Broadcasts progress and completion events via PubSub for real-time UI <br>updates<br> <li> Handles execution record creation and statistics tracking</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-06f4b3bf56e5f1d122b25040ea7f321125d6cae20606811dc0b2a0ddc7a66226">+669/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>target_criteria.ex</strong><dd><code>Device targeting DSL for sweep group configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/target_criteria.ex <ul><li>New DSL module for flexible device targeting with 14+ operators (eq, <br>neq, in, contains, in_cidr, etc.)<br> <li> Provides matching, filtering, and validation functions for device <br>criteria<br> <li> Converts criteria to Ash-compatible filter expressions<br> <li> Supports IP CIDR matching, tag-based filtering, and numeric <br>comparisons</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-a71ead6955e751bd5d27c6d81b1723147aebfbe4c80deedc20afbaed02afc062">+509/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_group.ex</strong><dd><code>Sweep group resource with scheduling and device targeting</code></dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_group.ex <ul><li>New Ash resource for user-configured sweep groups with independent <br>schedules<br> <li> Supports interval-based and cron-based scheduling<br> <li> Implements device targeting via <code>target_criteria</code> DSL and <code>static_targets</code><br> <li> Includes actions for managing targets, enabling/disabling groups, and <br>recording executions<br> <li> Enforces authorization policies for admin/operator roles</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-b7bfa2b8463be683eee7cf96abc97b135d225049d6507009f98a2e0ee658c728">+380/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>show.ex</strong><dd><code>Device detail view with network sweep status display</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/lib/serviceradar_web_ng_web/live/device_live/show.ex <ul><li>Adds sweep status section to device detail view showing latest sweep <br>results<br> <li> Displays device availability, response time, open ports, and sweep <br>history<br> <li> Loads sweep results by device IP from SweepHostResult records<br> <li> Includes UI components for status indicators, response times, and port <br>listings</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-92d8e0af57d2f65dfb8562e896dbea17ef9f47074ffd14f58261decc76f80c24">+220/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_compiler.ex</strong><dd><code>Sweep configuration compiler for agent deployment</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex <ul><li>Compiler module that transforms SweepGroup and SweepProfile resources <br>into agent-consumable config<br> <li> Merges profile settings with group overrides for ports, modes, and <br>concurrency<br> <li> Compiles targets from both static targets and device criteria<br> <li> Generates deterministic config hash for change detection</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-fd107fcbfd91022cd5377ad79bcce1796630a25c17386187f4fbf90b35f2c941">+294/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_group_execution.ex</strong><dd><code>Sweep execution tracking resource</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_group_execution.ex <ul><li>New Ash resource for tracking sweep group executions with status <br>lifecycle<br> <li> Records execution timing, host statistics, and agent information<br> <li> Includes actions for starting, completing, and failing executions<br> <li> Stores scanner performance metrics from agents<br> <li> Calculates success rate as a computed field</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-35d4958b8252ecd025abfb7526da27788c2a1f08a5e22883bc1ca517c1432eb0">+288/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep.ex</strong><dd><code>Sweep event processor with inventory update integration</code>&nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/event_writer/processors/sweep.ex <ul><li>Extends sweep event processor to update device inventory via <br>SweepResultsIngestor<br> <li> Processes inventory updates for both execution-tracked and non-tracked <br>sweep results<br> <li> Updates device availability status and adds "sweep" to <br>discovery_sources<br> <li> Handles graceful fallback when execution context is unavailable</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-ac15b7f40862a0f6bab14cfaf84ae8776c232baa90166014e34159a81427811e">+137/-1</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_monitor_worker.ex</strong><dd><code>Sweep group monitoring worker for missed execution detection</code></dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_monitor_worker.ex <ul><li>New Oban worker that monitors sweep groups for missed executions<br> <li> Detects overdue sweeps by comparing last_run_at against configured <br>interval plus grace period<br> <li> Publishes internal logs to <code>logs.internal.sweep</code> for missed sweeps<br> <li> Supports interval parsing (15m, 1h, 1d) and skips cron-based groups</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-c25f50cc2e496fc7f8f883a12098f278c56ae1d2d42d4d2af4200d364e2ef9a8">+238/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>grpc_server.rs</strong><dd><code>gRPC server response structure for sweep execution tracking</code></dd></summary> <hr> cmd/consumers/zen/src/grpc_server.rs <ul><li>Adds <code>execution_id</code> and <code>sweep_group_id</code> fields to gRPC response structure<br> <li> Enables agent to report sweep execution context back to server</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-e4564a93f6cf84ff91cd3d8141fc9272ec9b4ec19defd107afa42be01fcfed5b">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_cache.ex</strong><dd><code>ETS cache for compiled agent configurations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_cache.ex <ul><li>Implements ETS-based cache for compiled agent configurations with TTL <br>support<br> <li> Provides cache operations: <code>get</code>, <code>get_if_changed</code>, <code>put</code>, <code>invalidate</code>, and <br><code>clear_all</code><br> <li> Includes automatic cleanup of expired entries via periodic scheduled <br>task<br> <li> Supports hash-based change detection for efficient config updates</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-95f0c8640267167409c8af66d33550c2440b1ac5ec810f5e4d6fcd8df6ef8e2f">+234/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_host_result.ex</strong><dd><code>Ash resource for sweep host result tracking</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_host_result.ex <ul><li>Defines Ash resource for tracking per-host results from network sweep <br>executions<br> <li> Includes attributes for IP, hostname, status, response time, open <br>ports, and error messages<br> <li> Provides read actions filtered by execution, IP, and device with <br>sorting and pagination<br> <li> Implements authorization policies for admin and operator roles</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-6c0c4eb2ef2eeef5d76fd9fc1859240b7e3d4b1b2cdd6e72f86208c32c0086db">+237/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_server.ex</strong><dd><code>GenServer for config compilation orchestration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_server.ex <ul><li>GenServer that orchestrates config compilation and caching with <br>automatic cache invalidation<br> <li> Provides <code>get_config</code> and <code>get_config_if_changed</code> APIs for retrieving <br>compiled configurations<br> <li> Implements cache miss handling with compilation and database fallback<br> <li> Supports tenant-wide and type-specific cache invalidation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-a4d8447584bfcd1088465714c00bea67c90100320b125857ac5bd6a9783de468">+196/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_instance.ex</strong><dd><code>Ash resource for compiled config instances</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_instance.ex <ul><li>Ash resource representing compiled configuration instances for agents <br>or partitions<br> <li> Tracks version, content hash, delivery status, and source resource IDs<br> <li> Provides actions for creating, updating, and querying configs with <br>hash-based lookups<br> <li> Includes relationships to templates and version history with audit <br>capabilities</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-37fd443381f2242517ed060fccbc634e3f2500b06a8702393ad8e7e034caece2">+222/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_data_cleanup_worker.ex</strong><dd><code>Oban worker for sweep data retention cleanup</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_data_cleanup_worker.ex <ul><li>Oban worker that periodically deletes old sweep execution data to <br>prevent table growth<br> <li> Configurable retention periods for host results (default 7 days) and <br>executions (default 30 days)<br> <li> Processes deletions in batches to avoid locking large tables<br> <li> Filters to only delete completed/failed executions, preserving running <br>ones</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-b8660522af1e1ad3ba8da56f754567fdae03d09fa9e18749a3a49435893073fe">+175/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_profile.ex</strong><dd><code>Ash resource for network scanner profiles</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_profile.ex <ul><li>Ash resource for admin-managed scanner profiles defining reusable scan <br>configurations<br> <li> Includes attributes for ports, sweep modes, concurrency, timeout, and <br>ICMP/TCP settings<br> <li> Provides read actions to list available profiles and lookup by name<br> <li> Implements authorization restricting admin-only profiles to <br>administrators</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-4977d4fef97c317cd8f6f15b0762d96d856b4d42e5ae7bf1341e8f7ef26651e4">+215/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_pubsub.ex</strong><dd><code>PubSub broadcaster for sweep execution events</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_pubsub.ex <ul><li>PubSub broadcaster for sweep execution updates with tenant and <br>execution-specific topics<br> <li> Provides subscription and broadcast functions for execution lifecycle <br>events (started, progress, completed, failed)<br> <li> Includes safe broadcasting that gracefully handles unavailable PubSub<br> <li> Broadcasts detailed progress updates with host and device statistics</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-4c346d5c68dfcc3d45b42e115e1a0aebe410e2b1b28bda5eacec3a152fb3abfd">+166/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>rule_seeder.ex</strong><dd><code>GenServer for seeding default observability rules</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/observability/rule_seeder.ex <ul><li>GenServer that seeds default LogPromotionRules and StatefulAlertRules <br>for each tenant<br> <li> Creates rules for missed sweep detection and repeated missed sweep <br>alerts out of the box<br> <li> Checks for existing rules before seeding to avoid duplicates<br> <li> Includes configurable delay before seeding to allow system <br>initialization</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0518d428a7d0e3836a352c170a073dbc94bda8e024047f96a0e66ec4b892c166">+162/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>status_processor.ex</strong><dd><code>Add result buffering and telemetry to status processor</code>&nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_processor.ex <ul><li>Adds <code>forward</code> function with buffering support for failed result <br>deliveries<br> <li> Implements <code>StatusBuffer</code> integration to queue results when core is <br>unavailable<br> <li> Adds telemetry metrics for result forwarding with duration and result <br>status tracking<br> <li> Removes retry queueing logic in favor of explicit buffering strategy</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-2d04050dff3ba2cc8153559e33a892f5f421982bf6dcbda7172857b3bf398a02">+63/-11</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_template.ex</strong><dd><code>Ash resource for configuration templates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_template.ex <ul><li>Ash resource for reusable configuration templates with schema and <br>default values<br> <li> Supports tenant-specific and admin-only templates with enable/disable <br>flags<br> <li> Provides read actions filtered by config type and user role<br> <li> Includes relationships to config instances and unique name constraints <br>per tenant/type</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-1151a4c39db6cce97d7534f76218bd4ae14de0b1fff9cade8294c88ef67044a2">+163/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>status_buffer.ex</strong><dd><code>In-memory buffer for result payloads during outages</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_buffer.ex <ul><li>GenServer implementing bounded in-memory queue for buffering result <br>payloads during core outages<br> <li> Configurable via environment variables for max entries and flush <br>interval<br> <li> Periodically flushes buffered results back to core with exponential <br>backoff on failure<br> <li> Drops oldest entries when buffer reaches capacity to prevent unbounded <br>growth</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-557fa02eff90dabc3757fea28b49208bb07d03698bd485219bc7301058dbb2c4">+134/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_version.ex</strong><dd><code>Ash resource for config version history tracking</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_version.ex <ul><li>Ash resource tracking version history of configuration instances for <br>audit and rollback<br> <li> Stores compiled config, content hash, source IDs, and actor <br>information per version<br> <li> Provides read actions to retrieve version history and specific <br>versions by number<br> <li> Includes unique constraint on config instance and version number <br>combination</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-df079b1f5d9a8c20be9c34dcfc21f00b03bc12bc729b71471931de1e2aceff83">+158/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_publisher.ex</strong><dd><code>NATS publisher for config invalidation events</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_publisher.ex <ul><li>Publishes config invalidation events to NATS for cluster-wide cache <br>invalidation<br> <li> Invalidates local cache immediately and publishes structured events <br>with resource metadata<br> <li> Builds NATS subjects and payloads with tenant, config type, and change <br>information<br> <li> Gracefully handles NATS connection unavailability while ensuring local <br>cache is invalidated</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-40915f43b6db9aa1905de3743bad4cd08f0ccc183dc43a2dcc69ff2235eb5b8e">+119/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>agent_config_generator.ex</strong><dd><code>Integrate sweep config into agent payload generation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/edge/agent_config_generator.ex <ul><li>Adds <code>load_sweep_config</code> function to retrieve compiled sweep <br>configurations from ConfigServer<br> <li> Integrates sweep config into the full agent payload under the <code>sweep</code> <br>key<br> <li> Includes error handling and logging for missing or failed sweep config <br>loads<br> <li> Updates <code>build_config</code> to merge sweep config with sync payload and <br>recompute version hash</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-f368b9b41fa062759f00ff6fcae314cc5a42bb1caca82a9069103a803df1f9d7">+35/-5</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>compiler.ex</strong><dd><code>Behaviour definition for config compilers</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/compiler.ex <ul><li>Defines behaviour for config compilers that transform Ash resources <br>into agent-consumable JSON<br> <li> Includes registry of available compilers and lookup functions for <br>config types<br> <li> Provides <code>content_hash</code> function for SHA256-based change detection<br> <li> Documents compiler implementation requirements and source resource <br>tracking</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-c853b548702e07ffa78e0a371869cd58f00b8ec13836220866d34298e047cbcd">+102/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>create_version_history.ex</strong><dd><code>Ash change for automatic config version history</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/changes/create_version_history.ex <ul><li>Ash change that automatically creates version history records when <br>config instances are updated<br> <li> Captures pre-update state including config, hash, version, and source <br>IDs for audit trail<br> <li> Extracts actor information from context for change attribution<br> <li> Logs warnings but doesn't fail main operation if history creation <br>fails</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-edf276f4ea9f73468d843cc8db84191727a3e37c3e964146c29419e91f120d50">+81/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>template_seeder.ex</strong><dd><code>Add sweep monitoring templates and alert rules</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/observability/template_seeder.ex <ul><li>Adds <code>passthrough</code> log template for internal sweep logs with subject <br><code>logs.internal.sweep</code><br> <li> Adds <code>promote_missed_sweeps</code> log promotion rule to convert sweep.missed <br>events to observability events<br> <li> Adds <code>repeated_missed_sweeps</code> stateful alert rule to trigger alerts on <br>multiple missed executions<br> <li> Includes configuration for severity, category, and alert thresholds</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-23e36dfd159d88c93b9115b3a2879574bbf840e8177043375926287f7b06be0b">+49/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>monitoring.pb.ex</strong><dd><code>Extend sweep completion status with scanner stats</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/proto/monitoring.pb.ex <ul><li>Adds <code>execution_id</code> and <code>sweep_group_id</code> fields to <code>SweepCompletionStatus</code> <br>protobuf message<br> <li> Introduces new <code>SweepScannerStats</code> protobuf message with detailed <br>packet, ring, retry, and port statistics<br> <li> Includes rate limiting and packet drop metrics for network scanner <br>diagnostics<br> <li> Extends sweep completion reporting with scanner-level performance data</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-c4072f56f73e61992b2401a13598f9edcf11b255ba343c79cc629782b0b0dca0">+22/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config_invalidation_notifier.ex</strong><dd><code>Ash notifier for config cache invalidation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/agent_config/config_invalidation_notifier.ex <ul><li>Ash notifier that triggers cache invalidation when ConfigInstance or <br>ConfigTemplate resources change<br> <li> Publishes NATS events for cluster-wide invalidation on create, update, <br>and delete actions<br> <li> Extracts tenant ID and config type from records for targeted <br>invalidation<br> <li> Logs invalidation events for debugging and monitoring</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-5bc1a50cf1eb09eaaa1dd8df41fbe73c3524a1ec4db43d5d45548c63fab94f39">+70/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_jobs.ex</strong><dd><code>Ash domain for sweep job management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/sweep_jobs.ex <ul><li>Defines <code>SweepJobs</code> Ash domain for network sweep job management<br> <li> Documents sweep group organization, device targeting DSL, and resource <br>relationships<br> <li> Registers four resources: SweepProfile, SweepGroup, <br>SweepGroupExecution, SweepHostResult<br> <li> Configures authorization and AshAdmin integration for domain <br>management</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-60717aee243422185a36e6d154b70446bd696626d7bc79b809d0fbfd716cb5f0">+50/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sync_ingestor.ex</strong><dd><code>Add tags field support to sync ingestor</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/inventory/sync_ingestor.ex <ul><li>Adds <code>tags</code> field extraction and initialization in device update <br>processing<br> <li> Includes <code>tags</code> in both new device creation and update data structures<br> <li> Initializes <code>tags</code> as empty map in default update structure<br> <li> Supports tag data from sync payloads for device metadata enrichment</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-fdf70a310cef758f735fae943c2a33bc7f851a1c3d1ba66499e911fd2bc5611a">+3/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>5 files</summary><table> <tr> <td> <details> <summary><strong>sweep_results_flow_e2e_test.exs</strong><dd><code>Sweep Results Ingestion E2E Test</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/test/serviceradar/sweep_jobs/sweep_results_flow_e2e_test.exs <ul><li>New end-to-end integration test for sweep results ingestion workflow<br> <li> Tests device creation, updates, and availability status changes from <br>sweep results<br> <li> Validates execution statistics (hosts total, available, failed) and <br>device discovery source tracking<br> <li> Verifies host result records are properly created with correct status <br>and port information</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d400921d5787a7de52aab03e0725f804a083c2170bf62c4ede876bb4e73a5c70">+163/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_compiler_test.exs</strong><dd><code>Tests for sweep compiler and target criteria validation</code>&nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/test/serviceradar/sweep_jobs/sweep_compiler_test.exs <ul><li>Comprehensive test suite for sweep config generation and validation<br> <li> Tests config hashing, target criteria DSL operators, and <br>agent-consumable format<br> <li> Validates CIDR/IP parsing, port ranges, and sweep modes<br> <li> Documents expected structure for Go agent consumption</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-57f7deaf4baa32135b553fb62cb1679f9f40030a4459b4482df9452b6b964550">+363/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>target_criteria_test.exs</strong><dd><code>Unit tests for sweep target criteria matching</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/test/serviceradar/sweep_jobs/target_criteria_test.exs <ul><li>Unit tests for <code>TargetCriteria</code> module covering tag operators (<code>has_any</code>, <br><code>has_all</code>)<br> <li> Tests range and numeric comparison operators (<code>in_range</code>, <code>gt</code>, <code>gte</code>, <code>lt</code>, <br><code>lte</code>)<br> <li> Validates null checks and criteria validation error handling<br> <li> Tests Ash filter conversion for complex criteria expressions</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-4adb502a7438004de2a88b74dd152eaa1562e1e0e06f663c6cdb501037e6325c">+92/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>sweep_config_distribution_integration_test.exs</strong><dd><code>Integration test for sweep config distribution</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/test/serviceradar/edge/sweep_config_distribution_integration_test.exs <ul><li>Integration test verifying sweep configuration is included in agent <br>payload<br> <li> Creates test device, profile, and sweep group, then validates compiled <br>config contains expected targets and settings<br> <li> Tests end-to-end flow from resource creation through config <br>compilation to agent payload generation<br> <li> Validates config hash consistency between ConfigServer and <br>AgentConfigGenerator</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0ed0dbd0a4802e88423a72a294bf9ec2b573aee4f51ad317a5798822d323ece2">+103/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>networks_live_test.exs</strong><dd><code>Tests for network settings LiveView</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/test/serviceradar_web_ng_web/live/settings/networks_live_test.exs <ul><li>Tests for networks settings LiveView covering sweep groups and scanner <br>profiles<br> <li> Validates listing of sweep groups and profiles with tab switching<br> <li> Tests new sweep group and profile form rendering with UI elements<br> <li> Includes helper function for admin user registration and login</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-81736ddb52f2c677276e60e51726c3fd0fb96f6d4bd349649f0b381e28e48088">+72/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>4 files</summary><table> <tr> <td> <details> <summary><strong>20260111200000_add_sweep_jobs_tables.exs</strong><dd><code>Database migration for sweep jobs tables</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/priv/repo/tenant_migrations/20260111200000_add_sweep_jobs_tables.exs <ul><li>Creates four new tables for sweep jobs domain: <code>sweep_profiles</code>, <br><code>sweep_groups</code>, <code>sweep_group_executions</code>, <code>sweep_host_results</code><br> <li> Defines indexes for efficient querying by tenant, partition, agent, <br>and IP<br> <li> Establishes foreign key relationships between groups, executions, and <br>results<br> <li> Supports multi-tenancy with tenant_id partitioning</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-8fe0ddbd0de23b297d28025ec21429230db5315e12360e0080c3f4d62e118b8e">+254/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>20260111194010_add_agent_config_tables.exs</strong><dd><code>Database migration for agent configuration tables</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/priv/repo/tenant_migrations/20260111194010_add_agent_config_tables.exs <ul><li>Creates three new database tables for agent configuration management: <br><code>agent_config_versions</code>, <code>agent_config_templates</code>, and <br><code>agent_config_instances</code><br> <li> Adds <code>jdm_definition</code> field to <code>zen_rules</code> table for JDM rule definitions<br> <li> Establishes foreign key relationships and unique indexes for config <br>instance management<br> <li> Provides comprehensive down migration for rollback support</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-ab37e828b85ed413aaf09cb439ecb0f6e18de2fbedaa219307771390cb5a31f6">+199/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>application.ex</strong><dd><code>Register agent config and sweep job services</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/application.ex <ul><li>Adds <code>ServiceRadar.Observability.RuleSeeder</code> to supervision tree for <br>seeding default rules<br> <li> Adds <code>ServiceRadar.AgentConfig.ConfigCache</code> and <br><code>ServiceRadar.AgentConfig.ConfigServer</code> to supervision tree<br> <li> Adds <code>ServiceRadar.AgentConfig</code> and <code>ServiceRadar.SweepJobs</code> domains to <br>Ash configuration<br> <li> Includes conditional startup based on <code>repo_enabled</code> configuration</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-a9ffbf400b7f9b22cd8980c41286c54fe373f1f1a8684bb6a344a5fb39b178d0">+15/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.exs</strong><dd><code>Configure agent config domains and sweep job workers</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/config/config.exs <ul><li>Adds <code>ServiceRadar.AgentConfig</code> and <code>ServiceRadar.SweepJobs</code> to Ash <br>domains list<br> <li> Adds <code>maintenance</code> and <code>monitoring</code> queues to Oban configuration<br> <li> Registers <code>SweepDataCleanupWorker</code> cron job for 3 AM daily cleanup<br> <li> Registers <code>SweepMonitorWorker</code> cron job for 5-minute interval monitoring</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-42b3888cc53d9dcf4ebc45ec7fffb2c672b129bffe763b6c76de58e4678a13a8">+9/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Refactoring</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>srql_components.ex</strong><dd><code>Refactor SRQL builder to use QueryBuilderComponents</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/lib/serviceradar_web_ng_web/components/srql_components.ex <ul><li>Imports <code>QueryBuilderComponents</code> module for reusable query builder UI <br>components<br> <li> Renames all <code>srql_builder_pill</code> component calls to <code>query_builder_pill</code> <br>throughout the SRQL builder interface<br> <li> Updates approximately 15 component references to use the new naming <br>convention</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-aeeb844af4bc736fac30dab33b73c6d9023ffa5c75b79dacfbc287b61524ec59">+17/-33</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Formatting</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>show.ex</strong><dd><code>Format agent show live view code</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> web-ng/lib/serviceradar_web_ng_web/live/agent_live/show.ex <ul><li>Reformats Task.async calls for better code readability with proper <br>line breaks<br> <li> Splits long RPC call chains across multiple lines for improved <br>maintainability<br> <li> No functional changes, purely formatting improvements</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-5e622205d1abddd8ad7dcf7a8ca1be583804d622d3d38b75140e9b909cf0534a">+18/-6</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>72 files</summary><table> <tr> <td><strong>MODULE.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-6136fc12446089c3db7360e923203dd114b6a1466252e71667c6791c20fe6bdc">+1/-10</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>server.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33e">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-33b655d8730ae3e9c844ee280787d11f1b0d5343119188273f89558805f814ba">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>network-sweeps.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0d5e993b4386d18c84c19fd8ff631ce6fdda85d2057853d344bb8852d06301d3">+102/-0</a>&nbsp; </td> </tr> <tr> <td><strong>troubleshooting-guide.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-343b87dd0b430b3f99b16d32200c353bb6e3d7bbb185da3c1b3effc3a03e7f2a">+8/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>web-ui.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-7e4dc17ccc5b2fd5773b7e6c9583dbe0f597ab9ae21849a2ec0a31554a6337a3">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>application.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-fc8dfd7489f127775b1f0baf09cea0cdf77b825dd5f92540e126a43cb246f5b2">+3/-7</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>test.exs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d8fffec56a9bc7305c02bbdd424e21031ecbbb2c2e6ba07ba626e6043540e360">+3/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>agent_config.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-320ea8054916838bfb11d6279fb7ce58837ade6d9c9885368f02957be135c50b">+40/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>compute_config_hash.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-f2924c6800a1a9976c59b736ae3e76d86fe566d4e839a3790567439e6cd97631">+28/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>increment_version.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-7d5e93c82f641a45257a70d82b43dc9b7070be776ca1e79763a1b3cb5520cecf">+25/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>device.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-f5671d34fab1f3bdb0bcc4db602074e03c803bb379bb530c54da9925cae883f2">+8/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen_rule_seeder.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-020128ea25bd10b783725e19d2de73d039c5bfb116a80f13702617bf278e6801">+11/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>20260111210000_add_sweep_cleanup_indexes.exs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-e0f47d34ff9c8ed951c9622ce1a53c3fde4303bc78d169b81a15a4c260ebb788">+38/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>20260111220000_add_scanner_metrics_to_executions.exs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-e4097d73b5edaaea9c94a88208ed0129d51417f421ac76831e841879533d765a">+26/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>20260111231229_add_device_tags.exs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-8cfdc781c9f37c20321083bccf1d8ab766c6785994f2f95230d0791e61051543">+19/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>20260111194010.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-8b055d5e370b56c1b506d04de125f95900feea080b6dbb65a2c87a4f1372fada">+301/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111194010.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-f255ad01e3ed524511b41c148545fb03c7d4afb10346a98ac1f9add4e5a32286">+174/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111194010.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d5f8f29013579541b53860ceeaafbd56b92aa0fecd4453ad4e91662198850f14">+228/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111231229.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-78e268a6fea4614c894f4baad7fede56676b850ed71e622975f7fc77f9a648af">+533/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111231229.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-47f629e5e8998059a23f6e5d04717ab5b315112fa281a7f5adcd52a673a47255">+290/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111231229.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-23eeb3de41a3d19b2c9b49f504f8492dfc30b1f4f9e0311f01b11d2b5dddd918">+349/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111231229.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-da3812ca40cdd9a941f30de247b875e220e4a321076182a5d50d3cc3dc689cfd">+267/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111231229.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-46eab3c6900bb1afb267f88fbb160f779f0dbdde481b37eb5ecdee9b567d58c0">+212/-0</a>&nbsp; </td> </tr> <tr> <td><strong>20260111194010.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-ba377c2fa4c5eca11d37d65e828b2107f03f7e223bf0c9121024a85d5779505e">+246/-0</a>&nbsp; </td> </tr> <tr> <td><strong>design.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-1b189f75058c304d437050097d758deadb6af6b1f994880721aaf3ae121459e4">+75/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-46502c38a14321ad2d0e6fc7ee8fa53b8aa3fe16b2b83248202e5e894807ecb0">+20/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-89269af2d0f44461b0987d0ca90b7a1bbf0edf29d791e119756c188ab53ddb1a">+30/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-2d62279edd50504c053ff7ccb4a3bdd58c23eaaca83b0ee1a875e22e611d9cd2">+34/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>design.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-ae0ce4aff2c9c1b07bd6c8ead005aa62f2fdee4917f5d6f9692d5d5f372ab141">+377/-0</a>&nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-fa41dca6cbb84d9b96b44d8fa1939c1f2332e05a807ba219da16c3cc722e199a">+61/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d3cb7aa6f999ab423bf0621a680b50e4465c2f1c7feee10c94387c8423ad5cb4">+128/-0</a>&nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-187fbe69ad7dd8fe90e276f23db528b64e969571d5b08332135a72b26035f443">+39/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-6f67209188549f2e9024bef3e5a5f7096c6422ee4c101709403f695971e32841">+263/-0</a>&nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d2c9834a90b4210a504067222d6c2acbf36c07158a1b8377771f4e9bacadeb06">+102/-0</a>&nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0032fb7a412d25916760a7e4524a3336816a780ba684233004b16f4ca1a53f8c">+174/-0</a>&nbsp; </td> </tr> <tr> <td><strong>design.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-1f94911f8fd75341f7fb128c3ea4246e47e72ca4ca9de602b0b379de1fe8f06b">+31/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-4fd88ce5573471865061bc79d13426f318d72da0a05172e13353a8a033817daf">+14/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-50657f60f23abb24b4ef4409cfeea3e9ad6508c6b9bb87fade86163a210a6c0b">+29/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-96c3fe01773b9b778c68856616562cf7e7142efd7617a4bc00df1ba947d5ef72">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-9763df8132fa8d8919489fdfaf2434921b436714eb2aa276dca0ea4f92c02ec5">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>push_loop.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785">+45/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>server.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-3c51e5356a25859a3300ab62ed2494462feb2567ef69e6db3aa2bbc1032c1c5d">+134/-485</a></td> </tr> <tr> <td><strong>server_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-bd96a3d3da5a6788f2a8df14f82bd22b96dde41f7067d20484ca0b5abeaf264e">+42/-490</a></td> </tr> <tr> <td><strong>sweep_config_gateway.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0590118d93b1be7997233109d892bea154ed59038b8069290db99dc03080d307">+197/-0</a>&nbsp; </td> </tr> <tr> <td><strong>sweep_performance_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-77b4e4256ad3aa348ecec7e2548e00f7b23cdaccde4322f7ed18be4cc7d5148d">+7/-47</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>sweep_service.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-988114dc491154ea9a249227411b24e463212831cd81b43e0844862ce7a41343">+94/-34</a>&nbsp; </td> </tr> <tr> <td><strong>types.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-e3a3767c816cdb568a387a32243f7348046f1f3445549cc06368870c914496cd">+13/-12</a>&nbsp; </td> </tr> <tr> <td><strong>metrics.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-1c5e2a501867b2fe257cc13a4ec0a49edf5b97f7c8c1c511e0787803fa692314">+3/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>sweep.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-3cc7dd0e748c9f77be9e384fed2703ab77375716524b70860153b6a1abae27ca">+36/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>interfaces.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-63bb5df96cdf14d46b202d0b39c061af58a00a5f73660a9083d894e9f21128e2">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-adb8cc16a3f4d1d26cd3c2b0e878e5024d154711e4d65824ac9868c7ad6b14aa">+0/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>aggregation_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-0c6eb392da3429542428a3c3242866632ba312a670e5face8b1d295fca100d82">+9/-12</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>availability_logic_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-3602efd19c56c4c95cfde64c4e937d84773d7f2d529f184cfd89aaaec31eb852">+5/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>interfaces.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-9fcc03d2b04ba7aa9e3123be8b7a8c1e2f953b6bb4bae83884a6ae9335c178ff">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>sweeper.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d11dee1504de681453c5a04e22ae44d64820221ac6b84a1630d3a3929dace47a">+124/-557</a></td> </tr> <tr> <td><strong>sweeper_prune_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-8ce87b06d677d2de133ffe3c2973bf43e0d2d8b3bd1e120c393a7777b17a1c6f">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>sweeper_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-7714a2d9e6f06cd0d5944247437dda7796228b95a507478e92f0fc6e09179424">+2/-1062</a></td> </tr> <tr> <td><strong>tcp_optimization_test.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-1218aed38178a35178132b397803ada99d736109eacf734a56014f77631ea076">+6/-29</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>types.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-8f1a81192d004a38415447ef54a364471f7404d03d6c28fc4ade5f0e181bc1ca">+0/-77</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>monitoring.pb.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4">+270/-69</a></td> </tr> <tr> <td><strong>monitoring.proto</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-c4fc4e9693e4d2cf45697113ccca65b8c5ff18d2037e31ade411473533b36c2b">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>devices.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-3202f22fff6863ed7848a129c49e2323322462b379d896d3fca2e59aa6f7b4c5">+71/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.exs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-da0b524f083d350207155c247526098fdd68866d64e7e4eae3d96fdae31bcfb6">+6/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar_web_ng_web.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-bfeb4e8164b81e0ff846081b11224a63282c549425d41e300932d79d164155ae">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>query_builder_components.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-5886ef9439ff16d903c1e447d50fd81c93826b302ba0969f7117fba883e525b6">+24/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>settings_components.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-540f72cd405a3221e6f350afd04e644db83f0a504938a8907e94c417d070601e">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>ui_components.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-ea4114e247de47a7a6261975047564b934b013e368aa2a7546a7853a9eca0f86">+8/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>index.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-d0d22a987aa942ef25088696daea862e36bd8ca083ce29e4670f63e62947c62f">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>index.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-61d0262af13a42905ebbd793e83537e61bfc09493df1664a82eb2536980ee1cd">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>router.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-df516cd33165cd85914c1ccb3ff6511d3fe688d4a66498b99807958998c5d07c">+8/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>catalog.ex</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-80c07c25c17e48bf86860ec91db10256b7700a863d5371798e1893e741dc0e15">+11/-1</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-12 05:19:01 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2267#issuecomment-3736927598
Original created: 2026-01-12T05:19:01Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Query injection

Description: User-controlled criteria values are interpolated into an SRQL query string (full_query =
"in:devices #{srql_query} ...") and the escaping performed by escape_srql_value/1 may be
insufficient for SRQL grammar, potentially enabling SRQL injection or expensive-query/DoS
via crafted values. index.ex [2291-2436]

Referred Code
# Device count query using SRQL
defp get_matching_device_count(scope, criteria) do
  srql_query = criteria_to_srql_query(criteria)

  if srql_query == "" do
    nil
  else
    srql_module =
      Application.get_env(:serviceradar_web_ng, :srql_module, ServiceRadarWebNG.SRQL)

    full_query = ~s|in:devices #{srql_query} stats:"count() as total"|

    case srql_module.query(full_query, %{scope: scope}) do
      {:ok, %{"results" => [%{"total" => count} | _]}} when is_integer(count) -> count
      _ -> nil
    end
  end
end

defp criteria_to_srql_query(criteria) when criteria == %{}, do: ""




 ... (clipped 125 lines)
Ticket Compliance
🟡
🎫 #2248
🟢 Add a new Settings UI "Network" section where admins can manage scanner settings and
define which protocols/ports are available (e.g., scanner profiles).
Admins can view configured sweep jobs in Settings UI under Networks tab, and edit/delete
jobs, with operational status such as last run.
Admins can configure sweep jobs from the Settings/Admin UI (not only bulk edit), including
device selection criteria like IP range/CIDR, tags (discovery_sources), partition, etc.
🔴 Provide a UI to bulk edit devices (selection or entire inventory) to configure network
sweeper scope (CIDRs / /32 for single IP) and ports/protocols, generating a new agent
config.
In sweeper configuration, partition selection must be mandatory, with default always
available; agent selection optional and defaults to any available agent in the selected
partition.
Validate/implement the new results flow: agents send sweep results to agent-gateway;
agent-gateway forwards to core (RPC/chunking/streaming); core processes via
identity/reconciliation and enriches devices (e.g., availability), updating device
inventory tables.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: 🏷️
Missing audit logging: Critical state-changing actions (create/update/delete/enable-disable sweep groups and
profiles) do not emit audit log entries with user/tenant context and outcome.

Referred Code
@impl true
def handle_event("switch_tab", %{"tab" => tab}, socket) do
  active_tab =
    case tab do
      "groups" -> :groups
      "profiles" -> :profiles
      "active_scans" -> :active_scans
      _ -> socket.assigns.active_tab
    end

  {:noreply, assign(socket, :active_tab, active_tab)}
end

def handle_event("toggle_group", %{"id" => id}, socket) do
  scope = socket.assigns.current_scope

  case load_sweep_group(scope, id) do
    nil ->
      {:noreply, put_flash(socket, :error, "Sweep group not found")}

    group ->



 ... (clipped 113 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: 🏷️
Errors silently ignored: Multiple data-load failure paths swallow errors (returning []/nil) without logging or
actionable context, reducing debuggability and resilience.

Referred Code
defp load_sweep_groups(scope) do
  case Ash.read(SweepGroup, scope: scope) do
    {:ok, groups} -> groups
    {:error, _} -> []
  end
end

defp load_sweep_group(scope, id) do
  case Ash.get(SweepGroup, id, scope: scope) do
    {:ok, group} -> group
    {:error, _} -> nil
  end
end

defp load_sweep_profiles(scope) do
  case Ash.read(SweepProfile, scope: scope) do
    {:ok, profiles} -> profiles
    {:error, _} -> []
  end
end




 ... (clipped 27 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: 🏷️
Insufficient input validation: User-supplied criteria and static_targets are incorporated into an SRQL query string and
persisted without validation of allowed characters/formats (e.g., IP/CIDR/range),
increasing injection and malformed-input risk.

Referred Code
# Device count query using SRQL
defp get_matching_device_count(scope, criteria) do
  srql_query = criteria_to_srql_query(criteria)

  if srql_query == "" do
    nil
  else
    srql_module =
      Application.get_env(:serviceradar_web_ng, :srql_module, ServiceRadarWebNG.SRQL)

    full_query = ~s|in:devices #{srql_query} stats:"count() as total"|

    case srql_module.query(full_query, %{scope: scope}) do
      {:ok, %{"results" => [%{"total" => count} | _]}} when is_integer(count) -> count
      _ -> nil
    end
  end
end

defp criteria_to_srql_query(criteria) when criteria == %{}, do: ""




 ... (clipped 143 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: 🏷️
Log sensitivity risk: Logging inspect(reason) on config load failures may include sensitive details depending on
upstream errors and should be reviewed/normalized to avoid leaking secrets in logs.

Referred Code
defp load_sweep_config(agent_id, tenant_id) do
  # Use "default" partition for now - can be extended to support partitions later
  partition = "default"

  case ConfigServer.get_config(tenant_id, :sweep, partition, agent_id) do
    {:ok, entry} ->
      # Return the compiled config from the cache entry
      entry.config

    {:error, :no_config_found} ->
      # No sweep config defined for this agent - return empty config
      Logger.debug("No sweep config found for agent #{agent_id}")
      %{}

    {:error, reason} ->
      Logger.warning(
        "Failed to load sweep config for agent #{agent_id}: #{inspect(reason)}"
      )

      %{}
  end

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
- Requires Further Human Verification
🏷️ - Compliance label
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2267#issuecomment-3736927598 Original created: 2026-01-12T05:19:01Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/5077fbf2000ee5c2ec92ee191ab0199378848c9e --> Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=1>⚪</td> <td><details><summary><strong>Query injection </strong></summary><br> <b>Description:</b> User-controlled criteria values are interpolated into an SRQL query string (<code>full_query = </code><br><code>"in:devices #{srql_query} ..."</code>) and the escaping performed by <code>escape_srql_value/1</code> may be <br>insufficient for SRQL grammar, potentially enabling SRQL injection or expensive-query/DoS <br>via crafted values. <strong><a href='https://github.com/carverauto/serviceradar/pull/2267/files#diff-b2127e71582033bc6dfd2d7397f56bf43c1f7c613defffc504b3d8ee1e7406c4R2291-R2436'>index.ex [2291-2436]</a></strong><br> <details open><summary>Referred Code</summary> ```elixir # Device count query using SRQL defp get_matching_device_count(scope, criteria) do srql_query = criteria_to_srql_query(criteria) if srql_query == "" do nil else srql_module = Application.get_env(:serviceradar_web_ng, :srql_module, ServiceRadarWebNG.SRQL) full_query = ~s|in:devices #{srql_query} stats:"count() as total"| case srql_module.query(full_query, %{scope: scope}) do {:ok, %{"results" => [%{"total" => count} | _]}} when is_integer(count) -> count _ -> nil end end end defp criteria_to_srql_query(criteria) when criteria == %{}, do: "" ... (clipped 125 lines) ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>🟡</td> <td> <details> <summary>🎫 <a href=https://github.com/carverauto/serviceradar/issues/2248>#2248</a></summary> <table width='100%'><tbody> <tr><td rowspan=3>🟢</td> <td>Add a new Settings UI "Network" section where admins can manage scanner settings and <br>define which protocols/ports are available (e.g., scanner profiles).<br></td></tr> <tr><td>Admins can view configured sweep jobs in Settings UI under Networks tab, and edit/delete <br>jobs, with operational status such as last run.<br></td></tr> <tr><td>Admins can configure sweep jobs from the Settings/Admin UI (not only bulk edit), including <br>device selection criteria like IP range/CIDR, tags (discovery_sources), partition, etc.<br></td></tr> <tr><td rowspan=2>🔴</td> <td>Provide a UI to bulk edit devices (selection or entire inventory) to configure network <br>sweeper scope (CIDRs / /32 for single IP) and ports/protocols, generating a new agent <br>config.<br></td></tr> <tr><td>In sweeper configuration, partition selection must be mandatory, with <code>default</code> always <br>available; agent selection optional and defaults to any available agent in the selected <br>partition.<br></td></tr> <tr><td rowspan=1>⚪</td> <td>Validate/implement the new results flow: agents send sweep results to agent-gateway; <br>agent-gateway forwards to core (RPC/chunking/streaming); core processes via <br>identity/reconciliation and enriches devices (e.g., availability), updating device <br>inventory tables.<br></td></tr> </tbody></table> </details> </td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=2>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=3>🔴</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** 🏷️<br><a href='https://github.com/carverauto/serviceradar/pull/2267/files#diff-b2127e71582033bc6dfd2d7397f56bf43c1f7c613defffc504b3d8ee1e7406c4R234-R367'><strong>Missing audit logging</strong></a>: Critical state-changing actions (create/update/delete/enable-disable sweep groups and <br>profiles) do not emit audit log entries with user/tenant context and outcome.<br> <details open><summary>Referred Code</summary> ```elixir @impl true def handle_event("switch_tab", %{"tab" => tab}, socket) do active_tab = case tab do "groups" -> :groups "profiles" -> :profiles "active_scans" -> :active_scans _ -> socket.assigns.active_tab end {:noreply, assign(socket, :active_tab, active_tab)} end def handle_event("toggle_group", %{"id" => id}, socket) do scope = socket.assigns.current_scope case load_sweep_group(scope, id) do nil -> {:noreply, put_flash(socket, :error, "Sweep group not found")} group -> ... (clipped 113 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** 🏷️<br><a href='https://github.com/carverauto/serviceradar/pull/2267/files#diff-b2127e71582033bc6dfd2d7397f56bf43c1f7c613defffc504b3d8ee1e7406c4R1976-R2023'><strong>Errors silently ignored</strong></a>: Multiple data-load failure paths swallow errors (returning <code>[]</code>/<code>nil</code>) without logging or <br>actionable context, reducing debuggability and resilience.<br> <details open><summary>Referred Code</summary> ```elixir defp load_sweep_groups(scope) do case Ash.read(SweepGroup, scope: scope) do {:ok, groups} -> groups {:error, _} -> [] end end defp load_sweep_group(scope, id) do case Ash.get(SweepGroup, id, scope: scope) do {:ok, group} -> group {:error, _} -> nil end end defp load_sweep_profiles(scope) do case Ash.read(SweepProfile, scope: scope) do {:ok, profiles} -> profiles {:error, _} -> [] end end ... (clipped 27 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Security-First Input Validation and Data Handling</strong></summary><br> **Objective:** Ensure all data inputs are validated, sanitized, and handled securely to prevent <br>vulnerabilities<br> **Status:** 🏷️<br><a href='https://github.com/carverauto/serviceradar/pull/2267/files#diff-b2127e71582033bc6dfd2d7397f56bf43c1f7c613defffc504b3d8ee1e7406c4R2291-R2454'><strong>Insufficient input validation</strong></a>: User-supplied criteria and <code>static_targets</code> are incorporated into an SRQL query string and <br>persisted without validation of allowed characters/formats (e.g., IP/CIDR/range), <br>increasing injection and malformed-input risk.<br> <details open><summary>Referred Code</summary> ```elixir # Device count query using SRQL defp get_matching_device_count(scope, criteria) do srql_query = criteria_to_srql_query(criteria) if srql_query == "" do nil else srql_module = Application.get_env(:serviceradar_web_ng, :srql_module, ServiceRadarWebNG.SRQL) full_query = ~s|in:devices #{srql_query} stats:"count() as total"| case srql_module.query(full_query, %{scope: scope}) do {:ok, %{"results" => [%{"total" => count} | _]}} when is_integer(count) -> count _ -> nil end end end defp criteria_to_srql_query(criteria) when criteria == %{}, do: "" ... (clipped 143 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=1>⚪</td> <td><details> <summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** 🏷️<br><a href='https://github.com/carverauto/serviceradar/pull/2267/files#diff-f368b9b41fa062759f00ff6fcae314cc5a42bb1caca82a9069103a803df1f9d7R290-R310'><strong>Log sensitivity risk</strong></a>: Logging <code>inspect(reason)</code> on config load failures may include sensitive details depending on <br>upstream errors and should be reviewed/normalized to avoid leaking secrets in logs.<br> <details open><summary>Referred Code</summary> ```elixir defp load_sweep_config(agent_id, tenant_id) do # Use "default" partition for now - can be extended to support partitions later partition = "default" case ConfigServer.get_config(tenant_id, :sweep, partition, agent_id) do {:ok, entry} -> # Return the compiled config from the cache entry entry.config {:error, :no_config_found} -> # No sweep config defined for this agent - return empty config Logger.debug("No sweep config found for agent #{agent_id}") %{} {:error, reason} -> Logger.warning( "Failed to load sweep config for agent #{agent_id}: #{inspect(reason)}" ) %{} end ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /compliance --update_compliance=true --> </td></tr></tbody></table> <details><summary>Compliance status legend</summary> 🟢 - Fully Compliant<br> 🟡 - Partial Compliant<br> 🔴 - Not Compliant<br> ⚪ - Requires Further Human Verification<br> 🏷️ - Compliance label<br> </details>
qodo-code-review[bot] commented 2026-01-12 05:20:31 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2267#issuecomment-3736930460
Original created: 2026-01-12T05:20:31Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Fix SQL injection vulnerability

Refactor the function to use Ecto's schema tuple in from/2 to prevent a SQL
injection vulnerability caused by direct string interpolation of the
tenant_schema.

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_results_ingestor.ex [490-511]

 defp add_sweep_to_discovery_sources(device_uids, tenant_schema) do
-  if is_binary(tenant_schema) and String.match?(tenant_schema, ~r/^tenant_[a-z0-9_]+$/) do
-    sql = """
-    UPDATE #{tenant_schema}.ocsf_devices
-    SET discovery_sources = array_append(
-      COALESCE(discovery_sources, ARRAY[]::text[]),
-      'sweep'
-    )
-    WHERE uid = ANY($1)
-    AND NOT ('sweep' = ANY(COALESCE(discovery_sources, ARRAY[]::text[])))
-    """
-
-    _ = Repo.query(sql, [device_uids])
-    :ok
-  else
-    Logger.error("SweepResultsIngestor: invalid tenant schema for SQL update",
+  # Use Ecto's :prefix option to safely set the schema, preventing SQL injection.
+  from(d in {tenant_schema, "ocsf_devices"},
+    where: d.uid in ^device_uids and fragment("NOT (? = ANY(COALESCE(discovery_sources, ARRAY[]::text[])))", "sweep"),
+    update: [
+      set: [
+        discovery_sources:
+          fragment("array_append(COALESCE(discovery_sources, ARRAY[]::text[]), ?)", "sweep")
+      ]
+    ]
+  )
+  |> Repo.update_all([])
+  :ok
+rescue
+  e ->
+    Logger.error(
+      "SweepResultsIngestor: failed to add sweep discovery source: #{inspect(e)}",
       tenant_schema: inspect(tenant_schema)
     )
-
     :ok
-  end
 end
  • Apply / Chat
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical SQL injection vulnerability by interpolating tenant_schema directly into a raw query and proposes a secure fix using Ecto's built-in features.

High
High-level
Re-evaluate the custom agent configuration system

The custom system for agent configuration is overly complex. It should be
re-evaluated in favor of a simpler pattern or an existing tool to reduce
maintenance overhead.

Examples:

elixir/serviceradar_core/lib/serviceradar/agent_config/config_server.ex [1-196]
defmodule ServiceRadar.AgentConfig.ConfigServer do
  @moduledoc """
  GenServer that orchestrates config compilation and caching.

  Provides the main API for getting compiled configs with automatic
  caching, hash-based change detection, and cache invalidation.

  ## Usage

      # Get config for an agent

 ... (clipped 186 lines)
elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex [1-294]
defmodule ServiceRadar.AgentConfig.Compilers.SweepCompiler do
  @moduledoc """
  Compiler for sweep configurations.

  Transforms SweepGroup and SweepProfile Ash resources into agent-consumable
  sweep configuration format.

  ## Output Format

  The compiled config follows this structure:

 ... (clipped 284 lines)

Solution Walkthrough:

Before:

# Agent requests config
def get_config(tenant_id, type, partition, agent_id):
  # 1. Check ETS cache
  case ConfigCache.get(key) do
    {:ok, entry} -> return entry
    :miss ->
      # 2. On miss, compile the config
      compiler = Compiler.for(type)
      {:ok, compiled_config} = compiler.compile(...)

      # 3. Persist new version to DB
      ConfigInstance.update(...)
      ConfigVersion.create(...)

      # 4. Put in ETS cache
      ConfigCache.put(key, compiled_config)
      return compiled_config
  end

# Resource (e.g., SweepGroup) is updated
def after_update(resource):
  # 5. Invalidate local cache and publish NATS event for cluster-wide invalidation
  ConfigPublisher.publish_invalidation(resource.tenant_id, :sweep)

After:

# A simpler, on-demand compilation approach
def get_config(tenant_id, type, partition, agent_id):
  # 1. Check a simple cache (e.g., ETS or Redis)
  case Cache.get(key) do
    {:ok, entry} -> return entry
    :miss ->
      # 2. On miss, compile the config from DB state
      compiler = Compiler.for(type)
      {:ok, compiled_config} = compiler.compile(...)

      # 3. Put in cache with a TTL
      Cache.put(key, compiled_config, ttl: 5_minutes)
      return compiled_config
  end

# No proactive invalidation needed; cache expires via TTL.
# This avoids the complexity of NATS publishers/subscribers
# and persistent versioning tables.

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies the introduction of a large, complex custom configuration system and raises a valid architectural concern about its long-term maintenance and complexity, which impacts a significant portion of the PR.

High
General
Improve performance by filtering devices in database

Improve performance by converting target_criteria to an Ash filter using
TargetCriteria.to_ash_filter/1, allowing device filtering to occur at the
database level instead of in-memory.

elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex [239-252]

 defp get_targets_from_criteria(criteria, tenant_schema, actor) do
+  ash_filter = TargetCriteria.to_ash_filter(criteria)
+
   query =
     Device
     |> Ash.Query.for_read(:read, %{}, actor: actor, tenant: tenant_schema)
+    |> Ash.Query.filter(ash_filter)
+    |> Ash.Query.select([:ip])
 
   case Ash.read(query, authorize?: false) do
     {:ok, devices} ->
-      TargetCriteria.extract_targets(devices, criteria, [])
+      Enum.map(devices, & &1.ip)
 
     {:error, reason} ->
-      Logger.warning("SweepCompiler: failed to load devices - #{inspect(reason)}")
+      Logger.warning("SweepCompiler: failed to load devices for criteria - #{inspect(reason)}")
       []
   end
 end
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a major performance bottleneck that could lead to application failure and proposes an efficient solution by moving the filtering logic to the database.

High
Make flush batch size configurable

Make the flush batch size for the status buffer configurable via an environment
variable instead of using a hard-coded value.

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_buffer.ex [39-86]

 @impl true
 def init(_opts) do
   max_entries = env_int("GATEWAY_RESULTS_BUFFER_LIMIT", @default_max_entries)
   flush_interval_ms = env_int("GATEWAY_RESULTS_BUFFER_FLUSH_MS", @default_flush_interval_ms)
+  flush_batch_size = env_int("GATEWAY_RESULTS_BUFFER_BATCH_SIZE", @default_flush_batch_size)
 
   schedule_flush(flush_interval_ms)
 
   {:ok,
    %{
      queue: :queue.new(),
      max_entries: max_entries,
-     flush_interval_ms: flush_interval_ms
+     flush_interval_ms: flush_interval_ms,
+     flush_batch_size: flush_batch_size
    }}
 end
 
 @impl true
 def handle_info(:flush, state) do
-  {state, more?} = flush_queue(state, @default_flush_batch_size)
+  {state, more?} = flush_queue(state, state.flush_batch_size)
 
   if more? do
     Process.send_after(self(), :flush, 0)
   else
     schedule_flush(state.flush_interval_ms)
   end
 
   {:noreply, state}
 end
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion improves configurability by allowing the flush batch size to be set via an environment variable, which enhances operational flexibility.

Low
Possible issue
Prevent excessive memory usage

Add a recursion depth limit to fetch_all_uids_paginated/5 to prevent potential
server memory exhaustion when fetching a very large number of device UIDs.

web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1202-1235]

-defp fetch_all_uids_paginated(srql_module, scope, query, cursor, acc) do
+defp get_all_matching_uids(scope, query) do
+  srql_module = srql_module()
+  # Start with depth 0
+  fetch_all_uids_paginated(srql_module, scope, query, nil, [], 0)
+end
+
+# Limit recursion to 100 pages (100 * 1000 = 100,000 devices)
+@max_uid_fetch_depth 100
+
+defp fetch_all_uids_paginated(_srql_module, _scope, _query, _cursor, acc, depth)
+     when depth >= @max_uid_fetch_depth do
+  finalize_uid_acc(acc)
+end
+
+defp fetch_all_uids_paginated(srql_module, scope, query, cursor, acc, depth) do
   full_query = "in:devices #{query} limit:1000"
   opts = if cursor, do: %{scope: scope, cursor: cursor}, else: %{scope: scope}
 
   case srql_module.query(full_query, opts) do
     {:ok, %{"results" => results, "pagination" => pagination}} when is_list(results) ->
       uids =
         results
         |> Enum.filter(&is_map/1)
         |> Enum.map(fn row -> Map.get(row, "uid") || Map.get(row, "id") end)
         |> Enum.filter(&is_binary/1)
 
       next_cursor = Map.get(pagination, "next_cursor")
       new_acc = [uids | acc]
 
       if is_binary(next_cursor) do
-        fetch_all_uids_paginated(srql_module, scope, query, next_cursor, new_acc)
+        fetch_all_uids_paginated(srql_module, scope, query, next_cursor, new_acc, depth + 1)
       else
         finalize_uid_acc(new_acc)
       end
 
     {:ok, %{"results" => results}} when is_list(results) ->
       uids =
         results
         |> Enum.filter(&is_map/1)
         |> Enum.map(fn row -> Map.get(row, "uid") || Map.get(row, "id") end)
         |> Enum.filter(&is_binary/1)
 
       finalize_uid_acc([uids | acc])
 
     _ ->
       finalize_uid_acc(acc)
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential memory exhaustion issue from unbounded recursion and proposes a robust solution by adding a depth limit, which is a critical safeguard for server stability.

Medium
Add support for IPv6 CIDR matching

Add a new function clause to ip_matches_cidr?/3 to implement CIDR matching for
IPv6 addresses, as the current implementation only supports IPv4 and silently
fails for IPv6.

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/target_criteria.ex [320-329]

 defp ip_matches_cidr?(ip, network, mask)
      when tuple_size(ip) == 4 and tuple_size(network) == 4 and mask >= 0 and mask <= 32 do
   # IPv4
   ip_int = ipv4_to_int(ip)
   network_int = ipv4_to_int(network)
   mask_bits = bsl(0xFFFFFFFF, 32 - mask) &&& 0xFFFFFFFF
   (ip_int &&& mask_bits) == (network_int &&& mask_bits)
 end
 
+defp ip_matches_cidr?(ip, network, mask)
+     when tuple_size(ip) == 8 and tuple_size(network) == 8 and mask >= 0 and mask <= 128 do
+  # IPv6
+  ip_int = ipv6_to_int(ip)
+  network_int = ipv6_to_int(network)
+  mask_bits = bsl(-1, 128 - mask)
+  (ip_int &&& mask_bits) == (network_int &&& mask_bits)
+end
+
 defp ip_matches_cidr?(_ip, _network, _mask), do: false
 
+defp ipv4_to_int({a, b, c, d}) do
+  bsl(a, 24) + bsl(b, 16) + bsl(c, 8) + d
+end
+
+defp ipv6_to_int({a, b, c, d, e, f, g, h}) do
+  bsl(a, 112) + bsl(b, 96) + bsl(c, 80) + bsl(d, 64) +
+    bsl(e, 48) + bsl(f, 32) + bsl(g, 16) + h
+end
+
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the ip_matches_cidr? function lacks IPv6 support, which is a significant functional bug, and provides a correct implementation to fix it.

Medium
Prevent potential infinite loop in cleanup

Prevent a potential infinite loop in the do_cleanup_batch/8 function by
modifying the recursive batch deletion logic.

elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_data_cleanup_worker.ex [116-171]

 defp do_cleanup_batch(schema, table, resource, timestamp_field, cutoff, batch_size, extra_filter, acc) do
   # Build query to find IDs of records to delete
   base_query =
     from(r in {schema <> "." <> table, resource},
       where: field(r, ^timestamp_field) < ^cutoff,
       order_by: [asc: field(r, ^timestamp_field)],
       select: r.id,
       limit: ^batch_size
     )
 
   query =
     if extra_filter do
       extra_filter.(base_query)
     else
       base_query
     end
 
   case Repo.all(query) do
     [] ->
       # No more records to delete
       acc
 
-    ids when is_list(ids) ->
+    ids when is_list(ids) and ids != [] ->
       # Delete by IDs
       delete_query =
         from(r in {schema <> "." <> table, resource},
           where: r.id in ^ids
         )
 
       case Repo.delete_all(delete_query) do
         {count, _} ->
           Logger.debug(
             "SweepDataCleanupWorker: Deleted #{count} #{table} records from #{schema}"
           )
 
-          # Continue with next batch
-          do_cleanup_batch(
-            schema,
-            table,
-            resource,
-            timestamp_field,
-            cutoff,
-            batch_size,
-            extra_filter,
+          # Continue with next batch if we deleted a full batch
+          if count == batch_size do
+            do_cleanup_batch(
+              schema,
+              table,
+              resource,
+              timestamp_field,
+              cutoff,
+              batch_size,
+              extra_filter,
+              %{acc | deleted: acc.deleted + count}
+            )
+          else
             %{acc | deleted: acc.deleted + count}
-          )
+          end
+        {:error, _reason} ->
+          # Stop processing for this table on error
+          %{acc | errors: acc.errors + 1}
       end
+    _ ->
+      acc
   end
 rescue
   e ->
     Logger.warning(
       "SweepDataCleanupWorker: Error cleaning #{table} in #{schema}: #{inspect(e)}"
     )
 
     %{acc | errors: acc.errors + 1}
 end
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential infinite loop in the recursive batch deletion logic and provides a robust fix that handles partial deletions and errors, preventing a critical runtime issue.

Medium
Correct ETS match_spec for expiration cleanup

Correct the ETS match_spec for expired entry cleanup to properly match and
delete records, preventing a memory leak.

elixir/serviceradar_core/lib/serviceradar/agent_config/config_cache.ex [220-234]

 defp cleanup_expired do
   now = System.monotonic_time(:millisecond)
 
   # Delete all entries where expires_at < now
   match_spec = [
-    {{:_, :_, :"$1"}, [{:<, :"$1", now}], [true]}
+    {
+      {:"$1", :"$2", :"$3"},
+      [{:<, :"$3", now}],
+      [true]
+    }
   ]
 
   deleted = :ets.select_delete(@table_name, match_spec)
 
   if deleted > 0 do
     Logger.debug("ConfigCache: cleaned up #{deleted} expired entries")
   end
 end
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a bug where the ETS match spec for cleanup is wrong, which would lead to a memory leak. The proposed fix correctly matches the data structure and fixes the bug.

Medium
Fully reset bulk selection state

Update the clear_selection event handler to also reset the :select_all_matching
and :total_matching_count assigns to prevent inconsistent UI state.

web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [159-161]

 def handle_event("clear_selection", _params, socket) do
-  {:noreply, assign(socket, :selected_devices, MapSet.new())}
+  {:noreply,
+   socket
+   |> assign(:selected_devices, MapSet.new())
+   |> assign(:select_all_matching, false)
+   |> assign(:total_matching_count, nil)}
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a bug where clearing the selection leaves the UI in an inconsistent state and provides the correct fix to reset all related state variables.

Medium
Always return :ok on forward failure

Ensure the process/1 pipeline continues gracefully by always returning :ok from
the forward/2 function, even on failure.

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_processor.ex [74-87]

 {:error, reason} ->
   if buffer_on_failure and should_buffer?(status) do
     if Process.whereis(StatusBuffer) do
       StatusBuffer.enqueue(status)
     else
       Logger.debug("Results buffer unavailable; dropping status")
     end
 
     emit_forward_metrics(:buffered, status, from_buffer, started_at)
     :ok
   else
     emit_forward_metrics(:failed, status, from_buffer, started_at)
-    {:error, reason}
+    :ok
   end
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that returning an error tuple breaks the with chain in process/1, and proposes changing the return value to :ok to ensure the pipeline continues, which is a valid improvement for robustness.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2267#issuecomment-3736930460 Original created: 2026-01-12T05:20:31Z --- ## PR Code Suggestions ✨ <!-- 5077fbf --> Explore these optional code suggestions: <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>Security</td> <td> <details><summary>Fix SQL injection vulnerability</summary> ___ **Refactor the function to use Ecto's schema tuple in <code>from/2</code> to prevent a SQL <br>injection vulnerability caused by direct string interpolation of the <br><code>tenant_schema</code>.** [elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_results_ingestor.ex [490-511]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-06f4b3bf56e5f1d122b25040ea7f321125d6cae20606811dc0b2a0ddc7a66226R490-R511) ```diff defp add_sweep_to_discovery_sources(device_uids, tenant_schema) do - if is_binary(tenant_schema) and String.match?(tenant_schema, ~r/^tenant_[a-z0-9_]+$/) do - sql = """ - UPDATE #{tenant_schema}.ocsf_devices - SET discovery_sources = array_append( - COALESCE(discovery_sources, ARRAY[]::text[]), - 'sweep' - ) - WHERE uid = ANY($1) - AND NOT ('sweep' = ANY(COALESCE(discovery_sources, ARRAY[]::text[]))) - """ - - _ = Repo.query(sql, [device_uids]) - :ok - else - Logger.error("SweepResultsIngestor: invalid tenant schema for SQL update", + # Use Ecto's :prefix option to safely set the schema, preventing SQL injection. + from(d in {tenant_schema, "ocsf_devices"}, + where: d.uid in ^device_uids and fragment("NOT (? = ANY(COALESCE(discovery_sources, ARRAY[]::text[])))", "sweep"), + update: [ + set: [ + discovery_sources: + fragment("array_append(COALESCE(discovery_sources, ARRAY[]::text[]), ?)", "sweep") + ] + ] + ) + |> Repo.update_all([]) + :ok +rescue + e -> + Logger.error( + "SweepResultsIngestor: failed to add sweep discovery source: #{inspect(e)}", tenant_schema: inspect(tenant_schema) ) - :ok - end end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=0 --> <details><summary>Suggestion importance[1-10]: 10</summary> __ Why: The suggestion correctly identifies a critical SQL injection vulnerability by interpolating `tenant_schema` directly into a raw query and proposes a secure fix using Ecto's built-in features. </details></details></td><td align=center>High </td></tr><tr><td rowspan=1>High-level</td> <td> <details><summary>Re-evaluate the custom agent configuration system</summary> ___ **The custom system for agent configuration is overly complex. It should be <br>re-evaluated in favor of a simpler pattern or an existing tool to reduce <br>maintenance overhead.** ### Examples: <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-a4d8447584bfcd1088465714c00bea67c90100320b125857ac5bd6a9783de468R1-R196">elixir/serviceradar_core/lib/serviceradar/agent_config/config_server.ex [1-196]</a> </summary> ```elixir defmodule ServiceRadar.AgentConfig.ConfigServer do @moduledoc """ GenServer that orchestrates config compilation and caching. Provides the main API for getting compiled configs with automatic caching, hash-based change detection, and cache invalidation. ## Usage # Get config for an agent ... (clipped 186 lines) ``` </details> <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2267/files#diff-fd107fcbfd91022cd5377ad79bcce1796630a25c17386187f4fbf90b35f2c941R1-R294">elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex [1-294]</a> </summary> ```elixir defmodule ServiceRadar.AgentConfig.Compilers.SweepCompiler do @moduledoc """ Compiler for sweep configurations. Transforms SweepGroup and SweepProfile Ash resources into agent-consumable sweep configuration format. ## Output Format The compiled config follows this structure: ... (clipped 284 lines) ``` </details> ### Solution Walkthrough: #### Before: ```elixir # Agent requests config def get_config(tenant_id, type, partition, agent_id): # 1. Check ETS cache case ConfigCache.get(key) do {:ok, entry} -> return entry :miss -> # 2. On miss, compile the config compiler = Compiler.for(type) {:ok, compiled_config} = compiler.compile(...) # 3. Persist new version to DB ConfigInstance.update(...) ConfigVersion.create(...) # 4. Put in ETS cache ConfigCache.put(key, compiled_config) return compiled_config end # Resource (e.g., SweepGroup) is updated def after_update(resource): # 5. Invalidate local cache and publish NATS event for cluster-wide invalidation ConfigPublisher.publish_invalidation(resource.tenant_id, :sweep) ``` #### After: ```elixir # A simpler, on-demand compilation approach def get_config(tenant_id, type, partition, agent_id): # 1. Check a simple cache (e.g., ETS or Redis) case Cache.get(key) do {:ok, entry} -> return entry :miss -> # 2. On miss, compile the config from DB state compiler = Compiler.for(type) {:ok, compiled_config} = compiler.compile(...) # 3. Put in cache with a TTL Cache.put(key, compiled_config, ttl: 5_minutes) return compiled_config end # No proactive invalidation needed; cache expires via TTL. # This avoids the complexity of NATS publishers/subscribers # and persistent versioning tables. ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies the introduction of a large, complex custom configuration system and raises a valid architectural concern about its long-term maintenance and complexity, which impacts a significant portion of the PR. </details></details></td><td align=center>High </td></tr><tr><td rowspan=2>General</td> <td> <details><summary>Improve performance by filtering devices in database</summary> ___ **Improve performance by converting <code>target_criteria</code> to an Ash filter using <br><code>TargetCriteria.to_ash_filter/1</code>, allowing device filtering to occur at the <br>database level instead of in-memory.** [elixir/serviceradar_core/lib/serviceradar/agent_config/compilers/sweep_compiler.ex [239-252]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-fd107fcbfd91022cd5377ad79bcce1796630a25c17386187f4fbf90b35f2c941R239-R252) ```diff defp get_targets_from_criteria(criteria, tenant_schema, actor) do + ash_filter = TargetCriteria.to_ash_filter(criteria) + query = Device |> Ash.Query.for_read(:read, %{}, actor: actor, tenant: tenant_schema) + |> Ash.Query.filter(ash_filter) + |> Ash.Query.select([:ip]) case Ash.read(query, authorize?: false) do {:ok, devices} -> - TargetCriteria.extract_targets(devices, criteria, []) + Enum.map(devices, & &1.ip) {:error, reason} -> - Logger.warning("SweepCompiler: failed to load devices - #{inspect(reason)}") + Logger.warning("SweepCompiler: failed to load devices for criteria - #{inspect(reason)}") [] end end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=2 --> <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a major performance bottleneck that could lead to application failure and proposes an efficient solution by moving the filtering logic to the database. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Make flush batch size configurable</summary> ___ **Make the flush batch size for the status buffer configurable via an environment <br>variable instead of using a hard-coded value.** [elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_buffer.ex [39-86]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-557fa02eff90dabc3757fea28b49208bb07d03698bd485219bc7301058dbb2c4R39-R86) ```diff @impl true def init(_opts) do max_entries = env_int("GATEWAY_RESULTS_BUFFER_LIMIT", @default_max_entries) flush_interval_ms = env_int("GATEWAY_RESULTS_BUFFER_FLUSH_MS", @default_flush_interval_ms) + flush_batch_size = env_int("GATEWAY_RESULTS_BUFFER_BATCH_SIZE", @default_flush_batch_size) schedule_flush(flush_interval_ms) {:ok, %{ queue: :queue.new(), max_entries: max_entries, - flush_interval_ms: flush_interval_ms + flush_interval_ms: flush_interval_ms, + flush_batch_size: flush_batch_size }} end @impl true def handle_info(:flush, state) do - {state, more?} = flush_queue(state, @default_flush_batch_size) + {state, more?} = flush_queue(state, state.flush_batch_size) if more? do Process.send_after(self(), :flush, 0) else schedule_flush(state.flush_interval_ms) end {:noreply, state} end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=3 --> <details><summary>Suggestion importance[1-10]: 4</summary> __ Why: The suggestion improves configurability by allowing the flush batch size to be set via an environment variable, which enhances operational flexibility. </details></details></td><td align=center>Low </td></tr><tr><td rowspan=6>Possible issue</td> <td> <details><summary>Prevent excessive memory usage</summary> ___ **Add a recursion depth limit to <code>fetch_all_uids_paginated/5</code> to prevent potential <br>server memory exhaustion when fetching a very large number of device UIDs.** [web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [1202-1235]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7R1202-R1235) ```diff -defp fetch_all_uids_paginated(srql_module, scope, query, cursor, acc) do +defp get_all_matching_uids(scope, query) do + srql_module = srql_module() + # Start with depth 0 + fetch_all_uids_paginated(srql_module, scope, query, nil, [], 0) +end + +# Limit recursion to 100 pages (100 * 1000 = 100,000 devices) +@max_uid_fetch_depth 100 + +defp fetch_all_uids_paginated(_srql_module, _scope, _query, _cursor, acc, depth) + when depth >= @max_uid_fetch_depth do + finalize_uid_acc(acc) +end + +defp fetch_all_uids_paginated(srql_module, scope, query, cursor, acc, depth) do full_query = "in:devices #{query} limit:1000" opts = if cursor, do: %{scope: scope, cursor: cursor}, else: %{scope: scope} case srql_module.query(full_query, opts) do {:ok, %{"results" => results, "pagination" => pagination}} when is_list(results) -> uids = results |> Enum.filter(&is_map/1) |> Enum.map(fn row -> Map.get(row, "uid") || Map.get(row, "id") end) |> Enum.filter(&is_binary/1) next_cursor = Map.get(pagination, "next_cursor") new_acc = [uids | acc] if is_binary(next_cursor) do - fetch_all_uids_paginated(srql_module, scope, query, next_cursor, new_acc) + fetch_all_uids_paginated(srql_module, scope, query, next_cursor, new_acc, depth + 1) else finalize_uid_acc(new_acc) end {:ok, %{"results" => results}} when is_list(results) -> uids = results |> Enum.filter(&is_map/1) |> Enum.map(fn row -> Map.get(row, "uid") || Map.get(row, "id") end) |> Enum.filter(&is_binary/1) finalize_uid_acc([uids | acc]) _ -> finalize_uid_acc(acc) end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential memory exhaustion issue from unbounded recursion and proposes a robust solution by adding a depth limit, which is a critical safeguard for server stability. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Add support for IPv6 CIDR matching</summary> ___ **Add a new function clause to <code>ip_matches_cidr?/3</code> to implement CIDR matching for <br>IPv6 addresses, as the current implementation only supports IPv4 and silently <br>fails for IPv6.** [elixir/serviceradar_core/lib/serviceradar/sweep_jobs/target_criteria.ex [320-329]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-a71ead6955e751bd5d27c6d81b1723147aebfbe4c80deedc20afbaed02afc062R320-R329) ```diff defp ip_matches_cidr?(ip, network, mask) when tuple_size(ip) == 4 and tuple_size(network) == 4 and mask >= 0 and mask <= 32 do # IPv4 ip_int = ipv4_to_int(ip) network_int = ipv4_to_int(network) mask_bits = bsl(0xFFFFFFFF, 32 - mask) &&& 0xFFFFFFFF (ip_int &&& mask_bits) == (network_int &&& mask_bits) end +defp ip_matches_cidr?(ip, network, mask) + when tuple_size(ip) == 8 and tuple_size(network) == 8 and mask >= 0 and mask <= 128 do + # IPv6 + ip_int = ipv6_to_int(ip) + network_int = ipv6_to_int(network) + mask_bits = bsl(-1, 128 - mask) + (ip_int &&& mask_bits) == (network_int &&& mask_bits) +end + defp ip_matches_cidr?(_ip, _network, _mask), do: false +defp ipv4_to_int({a, b, c, d}) do + bsl(a, 24) + bsl(b, 16) + bsl(c, 8) + d +end + +defp ipv6_to_int({a, b, c, d, e, f, g, h}) do + bsl(a, 112) + bsl(b, 96) + bsl(c, 80) + bsl(d, 64) + + bsl(e, 48) + bsl(f, 32) + bsl(g, 16) + h +end + ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=5 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies that the `ip_matches_cidr?` function lacks IPv6 support, which is a significant functional bug, and provides a correct implementation to fix it. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Prevent potential infinite loop in cleanup</summary> ___ **Prevent a potential infinite loop in the <code>do_cleanup_batch/8</code> function by <br>modifying the recursive batch deletion logic.** [elixir/serviceradar_core/lib/serviceradar/sweep_jobs/sweep_data_cleanup_worker.ex [116-171]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-b8660522af1e1ad3ba8da56f754567fdae03d09fa9e18749a3a49435893073feR116-R171) ```diff defp do_cleanup_batch(schema, table, resource, timestamp_field, cutoff, batch_size, extra_filter, acc) do # Build query to find IDs of records to delete base_query = from(r in {schema <> "." <> table, resource}, where: field(r, ^timestamp_field) < ^cutoff, order_by: [asc: field(r, ^timestamp_field)], select: r.id, limit: ^batch_size ) query = if extra_filter do extra_filter.(base_query) else base_query end case Repo.all(query) do [] -> # No more records to delete acc - ids when is_list(ids) -> + ids when is_list(ids) and ids != [] -> # Delete by IDs delete_query = from(r in {schema <> "." <> table, resource}, where: r.id in ^ids ) case Repo.delete_all(delete_query) do {count, _} -> Logger.debug( "SweepDataCleanupWorker: Deleted #{count} #{table} records from #{schema}" ) - # Continue with next batch - do_cleanup_batch( - schema, - table, - resource, - timestamp_field, - cutoff, - batch_size, - extra_filter, + # Continue with next batch if we deleted a full batch + if count == batch_size do + do_cleanup_batch( + schema, + table, + resource, + timestamp_field, + cutoff, + batch_size, + extra_filter, + %{acc | deleted: acc.deleted + count} + ) + else %{acc | deleted: acc.deleted + count} - ) + end + {:error, _reason} -> + # Stop processing for this table on error + %{acc | errors: acc.errors + 1} end + _ -> + acc end rescue e -> Logger.warning( "SweepDataCleanupWorker: Error cleaning #{table} in #{schema}: #{inspect(e)}" ) %{acc | errors: acc.errors + 1} end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=6 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential infinite loop in the recursive batch deletion logic and provides a robust fix that handles partial deletions and errors, preventing a critical runtime issue. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Correct ETS match_spec for expiration cleanup</summary> ___ **Correct the ETS <code>match_spec</code> for expired entry cleanup to properly match and <br>delete records, preventing a memory leak.** [elixir/serviceradar_core/lib/serviceradar/agent_config/config_cache.ex [220-234]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-95f0c8640267167409c8af66d33550c2440b1ac5ec810f5e4d6fcd8df6ef8e2fR220-R234) ```diff defp cleanup_expired do now = System.monotonic_time(:millisecond) # Delete all entries where expires_at < now match_spec = [ - {{:_, :_, :"$1"}, [{:<, :"$1", now}], [true]} + { + {:"$1", :"$2", :"$3"}, + [{:<, :"$3", now}], + [true] + } ] deleted = :ets.select_delete(@table_name, match_spec) if deleted > 0 do Logger.debug("ConfigCache: cleaned up #{deleted} expired entries") end end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=7 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a bug where the ETS match spec for cleanup is wrong, which would lead to a memory leak. The proposed fix correctly matches the data structure and fixes the bug. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Fully reset bulk selection state</summary> ___ **Update the <code>clear_selection</code> event handler to also reset the <code>:select_all_matching</code> <br>and <code>:total_matching_count</code> assigns to prevent inconsistent UI state.** [web-ng/lib/serviceradar_web_ng_web/live/device_live/index.ex [159-161]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-261a01f4876e5984e1d9e9b38a3540675dfb0272abc30e6bdb2a4fa610353cc7R159-R161) ```diff def handle_event("clear_selection", _params, socket) do - {:noreply, assign(socket, :selected_devices, MapSet.new())} + {:noreply, + socket + |> assign(:selected_devices, MapSet.new()) + |> assign(:select_all_matching, false) + |> assign(:total_matching_count, nil)} end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly identifies a bug where clearing the selection leaves the UI in an inconsistent state and provides the correct fix to reset all related state variables. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Always return <code>:ok</code> on forward failure</summary> ___ **Ensure the <code>process/1</code> pipeline continues gracefully by always returning <code>:ok</code> from <br>the <code>forward/2</code> function, even on failure.** [elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/status_processor.ex [74-87]](https://github.com/carverauto/serviceradar/pull/2267/files#diff-2d04050dff3ba2cc8153559e33a892f5f421982bf6dcbda7172857b3bf398a02R74-R87) ```diff {:error, reason} -> if buffer_on_failure and should_buffer?(status) do if Process.whereis(StatusBuffer) do StatusBuffer.enqueue(status) else Logger.debug("Results buffer unavailable; dropping status") end emit_forward_metrics(:buffered, status, from_buffer, started_at) :ok else emit_forward_metrics(:failed, status, from_buffer, started_at) - {:error, reason} + :ok end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=9 --> <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly points out that returning an error tuple breaks the `with` chain in `process/1`, and proposes changing the return value to `:ok` to ensure the pipeline continues, which is a valid improvement for robustness. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!2653
No description provided.