Feat/stateful alert rule eng #2644

Merged
mfreeman451 merged 7 commits from refs/pull/2644/head into testing 2026-01-10 20:02:59 +00:00
mfreeman451 commented 2026-01-10 07:48:06 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2241
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2241
Original created: 2026-01-10T07:48:06Z
Original updated: 2026-01-10T20:03:03Z
Original head: carverauto/serviceradar:feat/stateful_alert_rule_eng
Original base: testing
Original merged: 2026-01-10T20:02:59Z 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


Description

  • Major refactoring from poller-based to gateway-based architecture with comprehensive terminology updates across Go and Elixir services

  • Agent push mode implementation: Refactored agent startup with simplified config loading, graceful shutdown, and push-based communication with gateway via new runPushMode() function

  • Stateful alert rule evaluation engine: Implemented GenServer-based alert engine with bucketed time-window analysis, ETS state snapshots, cooldown periods, and OCSF event generation

  • Agent gateway gRPC server: Multi-tenant secure server with mTLS certificate validation, agent enrollment, heartbeat handling, and service status processing

  • NATS infrastructure enhancements: Added account service initialization, credentials file support across multiple services (trapd, zen, otel, flowgger), and subject pattern matching with wildcard support

  • Multi-tenant process management: Implemented per-tenant Horde registries and DynamicSupervisors for process isolation via TenantRegistry module

  • Edge onboarding and certificate generation: Comprehensive onboarding packages context with component certificate generation and X.509 CA management

  • Poll orchestration: New module for distributed service check execution with gateway discovery and PollJob state management

  • SPIFFE/SPIRE integration: Added cluster authentication support with SSL/TLS configuration and certificate verification

  • Database schema migrations: Initial tenant schema with 40+ tables and OpenTelemetry hypertables with TimescaleDB support

  • Simplified message processing: Zen consumer refactored to output raw JSON instead of CloudEvents format

  • CLI and service naming updates: Renamed update-poller to update-gateway, added nats-bootstrap and admin subcommands

  • Removed legacy components: Deleted poller and sync services with associated Docker configurations and setup scripts


Diagram Walkthrough

flowchart LR
  Agent["Agent<br/>Push Mode"] -->|mTLS| AgentGW["Agent Gateway<br/>gRPC Server"]
  AgentGW -->|Tenant Isolation| TenantReg["Tenant Registry<br/>Horde"]
  TenantReg -->|Process Management| AlertEng["Stateful Alert<br/>Engine"]
  AlertEng -->|ETS State| DB[(Database<br/>Schema)]
  Gateway["Gateway<br/>Service"] -->|NATS| NATSAcct["NATS Account<br/>Service"]
  NATSAcct -->|Credentials| Creds["Credentials<br/>File"]
  PollOrch["Poll Orchestrator"] -->|Dispatch| Gateway
  EdgeOnboard["Edge Onboarding"] -->|Certificates| TenantCA["Tenant CA<br/>Generator"]
  SPIFFE["SPIFFE/SPIRE"] -->|mTLS Auth| Cluster["Distributed<br/>Cluster"]

File Walkthrough

Relevant files
Enhancement
28 files
main.go
Agent refactored to push mode with simplified config loading

cmd/agent/main.go

  • Refactored agent startup from KV-based configuration to direct file
    loading with embedded defaults fallback
  • Replaced edge onboarding and KV watch mechanisms with simplified
    loadConfig() function
  • Implemented new runPushMode() function for push-based agent
    communication with gateway
  • Added graceful shutdown handling with signal management and timeout
    protection
  • Introduced Version variable for build-time injection and agent
    enrollment reporting
+174/-74
main.go
NATS account service initialization and configuration       

cmd/data-services/main.go

  • Added NATS account service initialization with operator configuration
  • Implemented resolver path configuration from environment variables
    with fallback to config file
  • Added system account credentials file setup for NATS resolver client
  • Registered NATSAccountServiceServer in gRPC service registration
+68/-0   
main.go
CLI subcommands refactored for gateway and NATS operations

cmd/cli/main.go

  • Renamed update-poller subcommand to update-gateway
  • Added nats-bootstrap subcommand for NATS operations
  • Added admin subcommand routing with dispatchAdminCommand() function
  • Implemented RunAdminNatsCommand() dispatch for admin NATS operations
+16/-2   
main.go
SNMP service renamed from poller to gateway                           

cmd/checkers/snmp/main.go

  • Renamed SNMPPollerService to SNMPGatewayService
  • Renamed Poller struct to Gateway struct
+1/-1     
app.go
gRPC service registration renamed to agent gateway             

cmd/core/app/app.go

  • Renamed gRPC service registration from RegisterPollerServiceServer to
    RegisterAgentGatewayServiceServer
+1/-1     
config.rs
NATS credentials and subject pattern matching support       

cmd/consumers/zen/src/config.rs

  • Added nats_creds_file optional configuration field with validation
  • Implemented nats_creds_path() method to resolve credentials file path
    with support for absolute and relative paths
  • Updated subject patterns from "events.*" to "*.logs.*" format with
    wildcard matching support
  • Added subject_matches() function for NATS subject pattern matching
    with * and > wildcards
+80/-25 
nats_output.rs
OTEL NATS output with credentials and logs subject support

cmd/otel/src/nats_output.rs

  • Added logs_subject optional configuration for dedicated logs subject
  • Added creds_file optional field for NATS credentials authentication
  • Updated default subject from "events.otel" to "otel"
  • Implemented credentials file loading in NATS connection setup
+22/-5   
config.rs
OTEL configuration with NATS credentials support                 

cmd/otel/src/config.rs

  • Added logs_subject and creds_file optional TOML configuration fields
  • Updated default NATS subject from "events.otel" to "otel"
  • Added credentials file path parsing with empty string validation
  • Updated example configuration with new fields
+21/-3   
server.rs
Sysmon checker field renamed from poller to gateway           

cmd/checkers/sysmon/src/server.rs

  • Renamed poller_id field to gateway_id in GetStatus and GetResults log
    messages
  • Updated response field from poller_id to gateway_id in status and
    results responses
+6/-6     
message_processor.rs
Message processor simplified to output raw JSON                   

cmd/consumers/zen/src/message_processor.rs

  • Removed CloudEvents event building logic and dependencies
    (EventBuilder, Uuid, Url)
  • Simplified message processing to output raw context JSON instead of
    CloudEvents format
  • Removed event_type derivation from rules
+2/-16   
main.rs
Trapd NATS credentials and gateway field support                 

cmd/trapd/src/main.rs

  • Added NATS credentials file support with nats_creds_path() method
  • Implemented credentials file loading for both secure and non-secure
    NATS connections
  • Renamed poller_id to gateway_id in status and results responses
+23/-3   
nats_output.rs
Flowgger NATS output credentials support                                 

cmd/flowgger/src/flowgger/output/nats_output.rs

  • Added creds_file optional configuration field for NATS credentials
  • Implemented credentials file loading in NATS connection setup with
    empty string validation
+14/-0   
config.rs
Trapd configuration with NATS credentials support               

cmd/trapd/src/config.rs

  • Added nats_creds_file optional configuration field with validation
  • Implemented nats_creds_path() method to resolve credentials file path
  • Updated default subject from "snmp.traps" to "logs.snmp"
+22/-1   
grpc_server.rs
Zen gRPC server field renamed to gateway                                 

cmd/consumers/zen/src/grpc_server.rs

  • Renamed poller_id field to gateway_id in GetStatus and GetResults
    responses
+2/-2     
nats.rs
Zen NATS connection with credentials support                         

cmd/consumers/zen/src/nats.rs

  • Added NATS credentials file support via nats_creds_path() method
  • Implemented credentials file loading in NATS connection setup
+4/-0     
server.rs
RPerf checker response structure updated with gateway field

cmd/checkers/rperf-client/src/server.rs

  • Added gateway_id field to GetStatus and GetResults response structures
+2/-0     
setup.rs
OTEL setup logging enhancement                                                     

cmd/otel/src/setup.rs

  • Added debug logging for NATS credentials file configuration
+1/-0     
onboarding_packages.ex
Edge onboarding packages context with certificate generation

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

  • Implemented comprehensive Ash-based context module for edge onboarding
    packages
  • Provides CRUD operations with token generation, delivery, revocation,
    and soft-delete
  • Includes component certificate generation signed by tenant CA
  • Implements authorization checks and event recording for audit trail
+622/-0 
stateful_alert_engine.ex
Stateful alert rule evaluation engine with bucketing         

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

  • Implements a GenServer-based stateful alert evaluation engine for
    log/event rules with bucketed time-window analysis
  • Manages alert state snapshots in ETS with persistence to database,
    supporting cooldown periods and renotification
  • Provides rule matching logic for logs and events with flexible
    filtering by subject, service, severity, body, and attributes
  • Handles alert lifecycle including creation, recovery, and history
    recording with OCSF event generation
+960/-0 
agent_gateway_server.ex
Agent gateway gRPC server with mTLS multi-tenancy               

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex

  • Implements gRPC server for agent status push/streaming with
    multi-tenant security via mTLS certificate validation
  • Handles agent enrollment, heartbeat, and configuration delivery with
    component identity enforcement
  • Processes service status updates with comprehensive validation and
    forwarding to core cluster
  • Extracts tenant identity from client certificates and prevents
    spoofing through server-side metadata
+1020/-0
account_client.ex
NATS account management gRPC client                                           

elixir/serviceradar_core/lib/serviceradar/nats/account_client.ex

  • gRPC client for NATS account management with tenant isolation via
    JWT-based credentials
  • Supports account creation, user credential generation, JWT signing,
    and operator bootstrapping
  • Implements connection pooling with fallback to fresh channel creation
    and comprehensive error handling
  • Provides flexible configuration for account limits, subject mappings,
    and stream exports/imports
+621/-0 
agent.ex
Agent resource with state machine and capabilities             

elixir/serviceradar_core/lib/serviceradar/infrastructure/agent.ex

  • Ash resource for managing Go agents with OCSF v1.4.0 compliance and
    state machine lifecycle
  • Defines agent capabilities (ICMP, TCP, HTTP, gRPC, DNS, Process, SNMP)
    with metadata and health tracking
  • Implements state transitions (connecting, connected, degraded,
    disconnected, unavailable) with event publishing
  • Provides multi-tenant isolation, JSON API routes, and calculations for
    display/status information
+665/-0 
alert_generator.ex
Alert generation service for monitoring events                     

elixir/serviceradar_core/lib/serviceradar/monitoring/alert_generator.ex

  • New module providing alert generation service for monitoring events
    (service state changes, device availability, gateway/agent health,
    metric thresholds, stats anomalies)
  • Implements public API functions for creating alerts: service_down,
    service_recovered, device_offline, gateway_offline, agent_offline,
    threshold_violation, from_event, stats_anomaly
  • Handles alert persistence via Ash framework and webhook notifications
    via WebhookNotifier
  • Includes helper functions for metadata building, severity mapping, and
    event processing with support for alert configuration overrides
+609/-0 
tenant_registry.ex
Multi-tenant process registry and supervisor management   

elixir/serviceradar_core/lib/serviceradar/cluster/tenant_registry.ex

  • New module implementing per-tenant Horde registries and
    DynamicSupervisors for multi-tenant process isolation
  • Provides registry lifecycle management (ensure_registry,
    start_tenant_infrastructure, stop_tenant_infrastructure)
  • Implements slug-to-UUID mapping for admin/debug lookups via ETS table
  • Includes process registration, lookup, and selection APIs with
    convenience functions for gateways and agents
  • Defines TenantSupervisor child module managing per-tenant
    Horde.Registry and Horde.DynamicSupervisor instances
+634/-0 
alias_events.ex
Device alias lifecycle event tracking system                         

elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex

  • New module for device alias lifecycle event tracking, ported from Go
    core
  • Defines AliasRecord struct for parsing and comparing device alias
    metadata (service IDs, IPs, collectors)
  • Implements build_lifecycle_events to detect alias changes and generate
    audit events
  • Provides process_and_persist function to record alias sightings and
    manage state transitions
  • Includes helper functions for alias detection, deduplication, and
    event metadata building
+654/-0 
generator.ex
X.509 certificate generation for tenant CAs and edge         

elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex

  • New module for X.509 certificate generation for per-tenant CAs and
    edge components
  • Implements generate_tenant_ca to create long-lived intermediate CA
    certificates signed by platform root CA
  • Implements generate_component_cert to create short-lived edge
    component certificates with SPIFFE IDs
  • Provides certificate utilities: SPKI SHA-256 computation, CN
    extraction, tenant extraction from certificates
  • Uses Erlang :public_key module for all cryptographic operations with
    proper OID and extension handling
+541/-0 
spiffe.ex
SPIFFE/SPIRE integration for cluster authentication           

elixir/serviceradar_core/lib/serviceradar/spiffe.ex

  • New module providing SPIFFE/SPIRE integration for distributed cluster
    authentication
  • Implements SSL/TLS configuration functions for ERTS distribution,
    client, and server connections
  • Provides SPIFFE ID parsing, building, and verification with trust
    domain validation
  • Includes certificate expiry checking and file-based certificate
    rotation monitoring
  • Supports both filesystem and SPIRE Workload API modes with peer
    certificate verification callbacks
+564/-0 
poll_orchestrator.ex
Poll orchestration for distributed service checks               

elixir/serviceradar_core/lib/serviceradar/monitoring/poll_orchestrator.ex

  • New module orchestrating poll execution for scheduled service checks
    across distributed cluster
  • Implements execute_schedule to create PollJob records and dispatch
    checks to available gateways
  • Provides execute_schedule_async for asynchronous job execution with
    PubSub result broadcasting
  • Handles gateway discovery via Horde registries with support for
    multiple assignment modes (:any, :partition, :domain, :specific)
  • Manages PollJob state transitions through execution lifecycle with
    error handling and result aggregation
+450/-0 
Documentation
2 files
main.go
API documentation terminology update                                         

cmd/core/main.go

  • Updated API description from "service pollers" to "service gateways"
+1/-1     
main.go
Config sync tool documentation update                                       

cmd/tools/config-sync/main.go

  • Updated role flag help text from "poller" to "gateway"
+1/-1     
Database
1 files
20260107043446_initial_schema.exs
Initial tenant schema migration with core entities             

elixir/serviceradar_core/priv/repo/tenant_migrations/20260107043446_initial_schema.exs

  • Created comprehensive initial schema migration with 40+ tables for
    multi-tenant system
  • Includes tables for gateways, agents, devices, alerts, NATS
    credentials, edge onboarding, and monitoring
  • Defines relationships, indexes, and constraints for data integrity
  • Implements soft-delete patterns and encryption for sensitive data
+1416/-0
Tests
1 files
test_helper.exs
Agent gateway test helper initialization                                 

elixir/serviceradar_agent_gateway/test/test_helper.exs

  • Created test helper file for serviceradar_agent_gateway application
  • Documents automatic startup of PubSub, PollerRegistry, and
    AgentRegistry
+7/-0     
Configuration changes
1 files
20260108005841_add_otel_tables.exs
OpenTelemetry observability tables with TimescaleDB           

elixir/serviceradar_core/priv/repo/tenant_migrations/20260108005841_add_otel_tables.exs

  • Creates TimescaleDB hypertables for OpenTelemetry observability data
    (logs, metrics, traces)
  • Implements composite primary keys for hypertable partitioning and
    comprehensive indexing strategies
  • Defines materialized view for trace summaries with aggregation and
    filtering for 7-day retention
  • Supports multi-tenant isolation with tenant_id field and efficient
    time-series queries
+183/-0 
Additional files
101 files
.bazelignore +4/-0     
.bazelrc +5/-0     
.env-sample +33/-0   
.env.example +38/-0   
main.yml +18/-0   
AGENTS.md +177/-11
INSTALL.md +11/-11 
MODULE.bazel +5/-0     
Makefile +55/-14 
README-Docker.md +17/-2   
README.md +3/-3     
ROADMAP.md +1/-1     
BUILD.bazel +11/-6   
BUILD.bazel +12/-0   
mix_release.bzl +124/-49
BUILD.bazel +1/-0     
README.md +4/-4     
config.json +5/-6     
build.rs +0/-1     
monitoring.proto +3/-26   
README.md +2/-2     
monitoring.proto +2/-26   
Cargo.toml +0/-3     
README.md +8/-8     
zen-consumer-with-otel.json +9/-8     
zen-consumer.json +9/-8     
config.json +4/-4     
config.json +4/-4     
BUILD.bazel +1/-0     
README.md +3/-3     
README.md +9/-12   
flowgger.toml +2/-1     
otel.toml +3/-1     
otel.toml.example +5/-2     
BUILD.bazel +0/-25   
config.json +0/-111 
main.go +0/-138 
BUILD.bazel +0/-25   
config.json +0/-77   
main.go +0/-123 
README.md +3/-3     
docker-compose.elx.yml +117/-0 
docker-compose.spiffe.yml +8/-158 
docker-compose.yml +326/-269
Dockerfile.agent-gateway +94/-0   
Dockerfile.core-elx +108/-0 
Dockerfile.poller +0/-70   
Dockerfile.sync +0/-95   
Dockerfile.tools +1/-2     
Dockerfile.web-ng +6/-0     
agent-minimal.docker.json +6/-6     
agent.docker.json +5/-20   
agent.mtls.json +7/-10   
bootstrap-nested-spire.sh +0/-80   
.gitkeep +1/-0     
datasvc.docker.json +3/-2     
datasvc.mtls.json +14/-1   
db-event-writer.docker.json +11/-11 
db-event-writer.mtls.json +9/-8     
FRICTION_POINTS.md +0/-355 
README.md +0/-207 
SETUP_GUIDE.md +0/-307 
docker-compose.edge-e2e.yml +0/-27   
manage-packages.sh +0/-211 
setup-edge-e2e.sh +0/-198 
edge-poller-restart.sh +0/-178 
downstream-agent.conf +0/-32   
env +0/-4     
server.conf +0/-51   
upstream-agent.conf +0/-32   
entrypoint-certs.sh +13/-9   
entrypoint-poller.sh +0/-274 
entrypoint-sync.sh +0/-96   
fix-cert-permissions.sh +2/-2     
flowgger.docker.toml +3/-2     
generate-certs.sh +214/-12
nats.docker.conf +16/-160
netflow-consumer.mtls.json +1/-0     
otel.docker.toml +7/-2     
pg_hba.conf +9/-0     
pg_ident.conf +17/-0   
poller-stack.compose.yml +0/-121 
poller.docker.json +0/-128 
poller.mtls.json +0/-135 
poller.spiffe.json +0/-55   
refresh-upstream-credentials.sh +0/-248 
seed-poller-kv.sh +0/-83   
setup-edge-poller.sh +0/-204 
README.md +5/-5     
bootstrap-compose-spire.sh +0/-2     
ssl_dist.core.conf +17/-0   
ssl_dist.gateway.conf +17/-0   
ssl_dist.web.conf +17/-0   
sync.docker.json +0/-71   
sync.mtls.json +0/-75   
sysmon-osx.checker.json +1/-1     
tools-profile.sh +1/-2     
trapd.docker.json +3/-2     
update-config.sh +1/-190 
zen-install-rules.sh +4/-4     
Additional files not shown

Imported from GitHub pull request. Original GitHub pull request: #2241 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2241 Original created: 2026-01-10T07:48:06Z Original updated: 2026-01-10T20:03:03Z Original head: carverauto/serviceradar:feat/stateful_alert_rule_eng Original base: testing Original merged: 2026-01-10T20:02:59Z 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 ___ ### **Description** - **Major refactoring from poller-based to gateway-based architecture** with comprehensive terminology updates across Go and Elixir services - **Agent push mode implementation**: Refactored agent startup with simplified config loading, graceful shutdown, and push-based communication with gateway via new `runPushMode()` function - **Stateful alert rule evaluation engine**: Implemented GenServer-based alert engine with bucketed time-window analysis, ETS state snapshots, cooldown periods, and OCSF event generation - **Agent gateway gRPC server**: Multi-tenant secure server with mTLS certificate validation, agent enrollment, heartbeat handling, and service status processing - **NATS infrastructure enhancements**: Added account service initialization, credentials file support across multiple services (trapd, zen, otel, flowgger), and subject pattern matching with wildcard support - **Multi-tenant process management**: Implemented per-tenant Horde registries and DynamicSupervisors for process isolation via `TenantRegistry` module - **Edge onboarding and certificate generation**: Comprehensive onboarding packages context with component certificate generation and X.509 CA management - **Poll orchestration**: New module for distributed service check execution with gateway discovery and PollJob state management - **SPIFFE/SPIRE integration**: Added cluster authentication support with SSL/TLS configuration and certificate verification - **Database schema migrations**: Initial tenant schema with 40+ tables and OpenTelemetry hypertables with TimescaleDB support - **Simplified message processing**: Zen consumer refactored to output raw JSON instead of CloudEvents format - **CLI and service naming updates**: Renamed `update-poller` to `update-gateway`, added `nats-bootstrap` and `admin` subcommands - **Removed legacy components**: Deleted poller and sync services with associated Docker configurations and setup scripts ___ ### Diagram Walkthrough ```mermaid flowchart LR Agent["Agent<br/>Push Mode"] -->|mTLS| AgentGW["Agent Gateway<br/>gRPC Server"] AgentGW -->|Tenant Isolation| TenantReg["Tenant Registry<br/>Horde"] TenantReg -->|Process Management| AlertEng["Stateful Alert<br/>Engine"] AlertEng -->|ETS State| DB[(Database<br/>Schema)] Gateway["Gateway<br/>Service"] -->|NATS| NATSAcct["NATS Account<br/>Service"] NATSAcct -->|Credentials| Creds["Credentials<br/>File"] PollOrch["Poll Orchestrator"] -->|Dispatch| Gateway EdgeOnboard["Edge Onboarding"] -->|Certificates| TenantCA["Tenant CA<br/>Generator"] SPIFFE["SPIFFE/SPIRE"] -->|mTLS Auth| Cluster["Distributed<br/>Cluster"] ``` <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>28 files</summary><table> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>Agent refactored to push mode with simplified config loading</code></dd></summary> <hr> cmd/agent/main.go <ul><li>Refactored agent startup from KV-based configuration to direct file <br>loading with embedded defaults fallback<br> <li> Replaced edge onboarding and KV watch mechanisms with simplified <br><code>loadConfig()</code> function<br> <li> Implemented new <code>runPushMode()</code> function for push-based agent <br>communication with gateway<br> <li> Added graceful shutdown handling with signal management and timeout <br>protection<br> <li> Introduced <code>Version</code> variable for build-time injection and agent <br>enrollment reporting</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3">+174/-74</a></td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>NATS account service initialization and configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/data-services/main.go <ul><li>Added NATS account service initialization with operator configuration<br> <li> Implemented resolver path configuration from environment variables <br>with fallback to config file<br> <li> Added system account credentials file setup for NATS resolver client<br> <li> Registered <code>NATSAccountServiceServer</code> in gRPC service registration</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816">+68/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>CLI subcommands refactored for gateway and NATS operations</code></dd></summary> <hr> cmd/cli/main.go <ul><li>Renamed <code>update-poller</code> subcommand to <code>update-gateway</code><br> <li> Added <code>nats-bootstrap</code> subcommand for NATS operations<br> <li> Added <code>admin</code> subcommand routing with <code>dispatchAdminCommand()</code> function<br> <li> Implemented <code>RunAdminNatsCommand()</code> dispatch for admin NATS operations</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-ed4d81d29a7267f93fd77e17993fd3491b9ef6ded18490b4514d10ed1d803bc2">+16/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>SNMP service renamed from poller to gateway</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/checkers/snmp/main.go <ul><li>Renamed <code>SNMPPollerService</code> to <code>SNMPGatewayService</code><br> <li> Renamed <code>Poller</code> struct to <code>Gateway</code> struct</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-f25402eade63525184cb5e7437accff93c7b9338eebe81add6dc5f2a9eb12550">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>app.go</strong><dd><code>gRPC service registration renamed to agent gateway</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/core/app/app.go <ul><li>Renamed gRPC service registration from <code>RegisterPollerServiceServer</code> to <br><code>RegisterAgentGatewayServiceServer</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4ad8a289575edf3b163088617b7a40ae1305c29ced0c7d59b3751c57d6938072">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>NATS credentials and subject pattern matching support</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/config.rs <ul><li>Added <code>nats_creds_file</code> optional configuration field with validation<br> <li> Implemented <code>nats_creds_path()</code> method to resolve credentials file path <br>with support for absolute and relative paths<br> <li> Updated subject patterns from "events.*" to "*.logs.*" format with <br>wildcard matching support<br> <li> Added <code>subject_matches()</code> function for NATS subject pattern matching <br>with <code>*</code> and <code>></code> wildcards</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-05038f3867985e757de9027609950e682bad6d1992dac6acd7c28962a3c65dc4">+80/-25</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>nats_output.rs</strong><dd><code>OTEL NATS output with credentials and logs subject support</code></dd></summary> <hr> cmd/otel/src/nats_output.rs <ul><li>Added <code>logs_subject</code> optional configuration for dedicated logs subject<br> <li> Added <code>creds_file</code> optional field for NATS credentials authentication<br> <li> Updated default subject from "events.otel" to "otel"<br> <li> Implemented credentials file loading in NATS connection setup</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-6b585ea3564a481174e04da1270e2e13edd4e2b980d02a2652d6d21e6d82a498">+22/-5</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>OTEL configuration with NATS credentials support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/otel/src/config.rs <ul><li>Added <code>logs_subject</code> and <code>creds_file</code> optional TOML configuration fields<br> <li> Updated default NATS subject from "events.otel" to "otel"<br> <li> Added credentials file path parsing with empty string validation<br> <li> Updated example configuration with new fields</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-abbaec651da3d6af96b482e0f77bb909b65dbe0cabd78b5803769cc9dab0a1b0">+21/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server.rs</strong><dd><code>Sysmon checker field renamed from poller to gateway</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/checkers/sysmon/src/server.rs <ul><li>Renamed <code>poller_id</code> field to <code>gateway_id</code> in GetStatus and GetResults log <br>messages<br> <li> Updated response field from <code>poller_id</code> to <code>gateway_id</code> in status and <br>results responses</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33e">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>message_processor.rs</strong><dd><code>Message processor simplified to output raw JSON</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/message_processor.rs <ul><li>Removed CloudEvents event building logic and dependencies <br>(EventBuilder, Uuid, Url)<br> <li> Simplified message processing to output raw context JSON instead of <br>CloudEvents format<br> <li> Removed event_type derivation from rules</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9fcbc5358a9009e60a8cd22d21e5a9ea652787c727732d0b869e0865495114c3">+2/-16</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.rs</strong><dd><code>Trapd NATS credentials and gateway field support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/trapd/src/main.rs <ul><li>Added NATS credentials file support with <code>nats_creds_path()</code> method<br> <li> Implemented credentials file loading for both secure and non-secure <br>NATS connections<br> <li> Renamed <code>poller_id</code> to <code>gateway_id</code> in status and results responses</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-33b655d8730ae3e9c844ee280787d11f1b0d5343119188273f89558805f814ba">+23/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>nats_output.rs</strong><dd><code>Flowgger NATS output credentials support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/flowgger/src/flowgger/output/nats_output.rs <ul><li>Added <code>creds_file</code> optional configuration field for NATS credentials<br> <li> Implemented credentials file loading in NATS connection setup with <br>empty string validation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-a82e2e4d413539bf0b414b5629665b19648447523994cba639c4d1238aa5a0c1">+14/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>Trapd configuration with NATS credentials support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/trapd/src/config.rs <ul><li>Added <code>nats_creds_file</code> optional configuration field with validation<br> <li> Implemented <code>nats_creds_path()</code> method to resolve credentials file path<br> <li> Updated default subject from "snmp.traps" to "logs.snmp"</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c89b88ba4d2bf0a054d0ba69a672a92c30140b8d19503d67b980a218ffe3106d">+22/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>grpc_server.rs</strong><dd><code>Zen gRPC server field renamed to gateway</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/grpc_server.rs <ul><li>Renamed <code>poller_id</code> field to <code>gateway_id</code> in GetStatus and GetResults <br>responses</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e4564a93f6cf84ff91cd3d8141fc9272ec9b4ec19defd107afa42be01fcfed5b">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>nats.rs</strong><dd><code>Zen NATS connection with credentials support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/nats.rs <ul><li>Added NATS credentials file support via <code>nats_creds_path()</code> method<br> <li> Implemented credentials file loading in NATS connection setup</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-97f7335def0ad5d644b594a1076ae2d7080b11259cbb8de22c7946cc8e4b39f8">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server.rs</strong><dd><code>RPerf checker response structure updated with gateway field</code></dd></summary> <hr> cmd/checkers/rperf-client/src/server.rs <ul><li>Added <code>gateway_id</code> field to GetStatus and GetResults response structures</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bce0f4ca6548712f224b73816825d28e831acbbff7dbed3c98671ed50f65d028">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>setup.rs</strong><dd><code>OTEL setup logging enhancement</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; &nbsp; &nbsp; </dd></summary> <hr> cmd/otel/src/setup.rs - Added debug logging for NATS credentials file configuration </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-3891f667deb20fd26e296d3e2742c57378d3764fe1743118e612465ae360391f">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>onboarding_packages.ex</strong><dd><code>Edge onboarding packages context with certificate generation</code></dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/edge/onboarding_packages.ex <ul><li>Implemented comprehensive Ash-based context module for edge onboarding <br>packages<br> <li> Provides CRUD operations with token generation, delivery, revocation, <br>and soft-delete<br> <li> Includes component certificate generation signed by tenant CA<br> <li> Implements authorization checks and event recording for audit trail</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e4fe8e19bc324416302bb4c962f57133b3f62eb82053766844d881c522a473e5">+622/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>stateful_alert_engine.ex</strong><dd><code>Stateful alert rule evaluation engine with bucketing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/observability/stateful_alert_engine.ex <ul><li>Implements a GenServer-based stateful alert evaluation engine for <br>log/event rules with bucketed time-window analysis<br> <li> Manages alert state snapshots in ETS with persistence to database, <br>supporting cooldown periods and renotification<br> <li> Provides rule matching logic for logs and events with flexible <br>filtering by subject, service, severity, body, and attributes<br> <li> Handles alert lifecycle including creation, recovery, and history <br>recording with OCSF event generation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bae3a52db882de8c947e62f219a95dff8db4e155e37d9a361dbe14ec25fcd3bd">+960/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>agent_gateway_server.ex</strong><dd><code>Agent gateway gRPC server with mTLS multi-tenancy</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex <ul><li>Implements gRPC server for agent status push/streaming with <br>multi-tenant security via mTLS certificate validation<br> <li> Handles agent enrollment, heartbeat, and configuration delivery with <br>component identity enforcement<br> <li> Processes service status updates with comprehensive validation and <br>forwarding to core cluster<br> <li> Extracts tenant identity from client certificates and prevents <br>spoofing through server-side metadata</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-369a368073dc8ec1140bcea699005a1ce97a90cd59629df0bd18c71c7ffaae9f">+1020/-0</a></td> </tr> <tr> <td> <details> <summary><strong>account_client.ex</strong><dd><code>NATS account management gRPC client</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/nats/account_client.ex <ul><li>gRPC client for NATS account management with tenant isolation via <br>JWT-based credentials<br> <li> Supports account creation, user credential generation, JWT signing, <br>and operator bootstrapping<br> <li> Implements connection pooling with fallback to fresh channel creation <br>and comprehensive error handling<br> <li> Provides flexible configuration for account limits, subject mappings, <br>and stream exports/imports</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2e18ac777ac600b12982ba9e9d5327e23ebd84c139a2add7976f8bf61283e554">+621/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>agent.ex</strong><dd><code>Agent resource with state machine and capabilities</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/infrastructure/agent.ex <ul><li>Ash resource for managing Go agents with OCSF v1.4.0 compliance and <br>state machine lifecycle<br> <li> Defines agent capabilities (ICMP, TCP, HTTP, gRPC, DNS, Process, SNMP) <br>with metadata and health tracking<br> <li> Implements state transitions (connecting, connected, degraded, <br>disconnected, unavailable) with event publishing<br> <li> Provides multi-tenant isolation, JSON API routes, and calculations for <br>display/status information</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c56f92b6ce744cab3f2dc00dde92e2017cffdd12ad4618f7fa720252f2a6843a">+665/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>alert_generator.ex</strong><dd><code>Alert generation service for monitoring events</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/monitoring/alert_generator.ex <ul><li>New module providing alert generation service for monitoring events <br>(service state changes, device availability, gateway/agent health, <br>metric thresholds, stats anomalies)<br> <li> Implements public API functions for creating alerts: <code>service_down</code>, <br><code>service_recovered</code>, <code>device_offline</code>, <code>gateway_offline</code>, <code>agent_offline</code>, <br><code>threshold_violation</code>, <code>from_event</code>, <code>stats_anomaly</code><br> <li> Handles alert persistence via Ash framework and webhook notifications <br>via <code>WebhookNotifier</code><br> <li> Includes helper functions for metadata building, severity mapping, and <br>event processing with support for alert configuration overrides</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-62074160ac91002a439bab337a032329681bc55c84a59ab9934bc76d05a5de04">+609/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>tenant_registry.ex</strong><dd><code>Multi-tenant process registry and supervisor management</code>&nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/cluster/tenant_registry.ex <ul><li>New module implementing per-tenant Horde registries and <br>DynamicSupervisors for multi-tenant process isolation<br> <li> Provides registry lifecycle management (<code>ensure_registry</code>, <br><code>start_tenant_infrastructure</code>, <code>stop_tenant_infrastructure</code>)<br> <li> Implements slug-to-UUID mapping for admin/debug lookups via ETS table<br> <li> Includes process registration, lookup, and selection APIs with <br>convenience functions for gateways and agents<br> <li> Defines <code>TenantSupervisor</code> child module managing per-tenant <br>Horde.Registry and Horde.DynamicSupervisor instances</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-91248b3b128a2e3d9bea6ffdb5e0f295e4a1745e82f87687c640ad01416fb85d">+634/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>alias_events.ex</strong><dd><code>Device alias lifecycle event tracking system</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex <ul><li>New module for device alias lifecycle event tracking, ported from Go <br>core<br> <li> Defines <code>AliasRecord</code> struct for parsing and comparing device alias <br>metadata (service IDs, IPs, collectors)<br> <li> Implements <code>build_lifecycle_events</code> to detect alias changes and generate <br>audit events<br> <li> Provides <code>process_and_persist</code> function to record alias sightings and <br>manage state transitions<br> <li> Includes helper functions for alias detection, deduplication, and <br>event metadata building</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bc3743067ea774f59bc5665770f7110a2d6e90f6e1156a7717a1c287f8979d28">+654/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>generator.ex</strong><dd><code>X.509 certificate generation for tenant CAs and edge</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex <ul><li>New module for X.509 certificate generation for per-tenant CAs and <br>edge components<br> <li> Implements <code>generate_tenant_ca</code> to create long-lived intermediate CA <br>certificates signed by platform root CA<br> <li> Implements <code>generate_component_cert</code> to create short-lived edge <br>component certificates with SPIFFE IDs<br> <li> Provides certificate utilities: SPKI SHA-256 computation, CN <br>extraction, tenant extraction from certificates<br> <li> Uses Erlang <code>:public_key</code> module for all cryptographic operations with <br>proper OID and extension handling</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-b48e4a9e1189da61e2a60e16f56fce81298d76b7cdab745107140fed3f6e48b4">+541/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>spiffe.ex</strong><dd><code>SPIFFE/SPIRE integration for cluster authentication</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/spiffe.ex <ul><li>New module providing SPIFFE/SPIRE integration for distributed cluster <br>authentication<br> <li> Implements SSL/TLS configuration functions for ERTS distribution, <br>client, and server connections<br> <li> Provides SPIFFE ID parsing, building, and verification with trust <br>domain validation<br> <li> Includes certificate expiry checking and file-based certificate <br>rotation monitoring<br> <li> Supports both filesystem and SPIRE Workload API modes with peer <br>certificate verification callbacks</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0cb8d921c19f671b66f91c0978e351e71d927c5f4694924984c9f1ed34d7ee78">+564/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>poll_orchestrator.ex</strong><dd><code>Poll orchestration for distributed service checks</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/monitoring/poll_orchestrator.ex <ul><li>New module orchestrating poll execution for scheduled service checks <br>across distributed cluster<br> <li> Implements <code>execute_schedule</code> to create PollJob records and dispatch <br>checks to available gateways<br> <li> Provides <code>execute_schedule_async</code> for asynchronous job execution with <br>PubSub result broadcasting<br> <li> Handles gateway discovery via Horde registries with support for <br>multiple assignment modes (<code>:any</code>, <code>:partition</code>, <code>:domain</code>, <code>:specific</code>)<br> <li> Manages PollJob state transitions through execution lifecycle with <br>error handling and result aggregation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-68a63639fc9d92d29501700c6604921098c9bbbf21e54f9148c1109c17c9c6d4">+450/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>2 files</summary><table> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>API documentation terminology update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/core/main.go <ul><li>Updated API description from "service pollers" to "service gateways"</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4ab3fd1d4debc53dd2499d94a0f60c648fdae4235dd1e3678095a975f5bb434a">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>Config sync tool documentation update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/tools/config-sync/main.go - Updated role flag help text from "poller" to "gateway" </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bc6eeb1b05bcb9179525e32fac1de9926b5823ec3504be546ab10c5c9740f544">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Database</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>20260107043446_initial_schema.exs</strong><dd><code>Initial tenant schema migration with core entities</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/priv/repo/tenant_migrations/20260107043446_initial_schema.exs <ul><li>Created comprehensive initial schema migration with 40+ tables for <br>multi-tenant system<br> <li> Includes tables for gateways, agents, devices, alerts, NATS <br>credentials, edge onboarding, and monitoring<br> <li> Defines relationships, indexes, and constraints for data integrity<br> <li> Implements soft-delete patterns and encryption for sensitive data</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0d217dc9822fab0d3390e8ec21040f98e67106e5c9126e043a9b701efcbfb576">+1416/-0</a></td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>test_helper.exs</strong><dd><code>Agent gateway test helper initialization</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_agent_gateway/test/test_helper.exs <ul><li>Created test helper file for serviceradar_agent_gateway application<br> <li> Documents automatic startup of PubSub, PollerRegistry, and <br>AgentRegistry</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-438a6880b1ce83b9bc82deacc98898368920b156c596305b0cdf6b8ff0ed7b06">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>20260108005841_add_otel_tables.exs</strong><dd><code>OpenTelemetry observability tables with TimescaleDB</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/priv/repo/tenant_migrations/20260108005841_add_otel_tables.exs <ul><li>Creates TimescaleDB hypertables for OpenTelemetry observability data <br>(logs, metrics, traces)<br> <li> Implements composite primary keys for hypertable partitioning and <br>comprehensive indexing strategies<br> <li> Defines materialized view for trace summaries with aggregation and <br>filtering for 7-day retention<br> <li> Supports multi-tenant isolation with tenant_id field and efficient <br>time-series queries</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d132d80b315ba243aadbce44609bad934cbe0109f71a8f2ac73133a8043c8051">+183/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>101 files</summary><table> <tr> <td><strong>.bazelignore</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-a5641cd37d6ad98b32cdfce1980836cc68312277bc6a7052f55da02ada5bc6cf">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>.bazelrc</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-544556920c45b42cbfe40159b082ce8af6bd929e492d076769226265f215832f">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>.env-sample</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c4368a972a7fa60d9c4e333cebf68cdb9a67acb810451125c02e3b7eb2594e3d">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>.env.example</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-a3046da0d15a27e89f2afe639b25748a7ad4d9290af3e7b1b6c1a5533c8f0a8c">+38/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-7829468e86c1cc5d5133195b5cb48e1ff6c75e3e9203777f6b2e379d9e4882b3">+18/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>AGENTS.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-a54ff182c7e8acf56acfd6e4b9c3ff41e2c41a31c9b211b2deb9df75d9a478f9">+177/-11</a></td> </tr> <tr> <td><strong>INSTALL.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-09b140a43ebfdd8dbec31ce72cafffd15164d2860fd390692a030bcb932b54a0">+11/-11</a>&nbsp; </td> </tr> <tr> <td><strong>MODULE.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-6136fc12446089c3db7360e923203dd114b6a1466252e71667c6791c20fe6bdc">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>Makefile</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52">+55/-14</a>&nbsp; </td> </tr> <tr> <td><strong>README-Docker.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9fd61d24482efe68c22d8d41e2a1dcc440f39195aa56e7a050f2abe598179efd">+17/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>ROADMAP.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-683343bdf93f55ed3cada86151abb8051282e1936e58d4e0a04beca95dff6e51">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-884fa9353a5226345e44fbabea3300efc7a87dfbcde0b6a42521ca51823f1b68">+11/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0e80ea46aeb61a873324685edb96eae864c7a2004fbb7ee404b4ec951190ba10">+12/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>mix_release.bzl</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-86ec281f99363b6b6eb1f49e21d83b7eeca93a35b552b9f305fffc6855e38ccd">+124/-49</a></td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-143f8d1549d52f28906f19ce28e5568a5be474470ff103c2c1e63c3e6b08d670">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bfd308915d0cf522e7fc76600dee687617dc69165ab22502a1d219850c0c0860">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-5b1bc8fe77422534739bdd3a38dc20d2634a86c171265c34e1b5d0c5a61b6bab">+5/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>build.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-251e7a923f45f8f903e510d10f183366bda06d281c8ecc3669e1858256e2186d">+0/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>monitoring.proto</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-b56f709f4a0a3db694f2124353908318631f23e20b7846bc4b8ee869e2e0632a">+3/-26</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2e9751b437fa61442aac074c7a4a912d0ac50ac3ea156ac8aedd8478d21c6bdb">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>monitoring.proto</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9faf6025eb0d3d38383f5b7ad2b733abeb38454d5e4de3e83994e94b12d87a50">+2/-26</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Cargo.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-fcf0c672917b64a5b953a914af013f16dddd6a1d813810236364e32f1ae70382">+0/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-643d2c3959322902c5bc9a22666b1e9ef71fa0bb87c9451b0e4147a4d5b51987">+8/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer-with-otel.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-68375f1f7847e1fbdf75664f6be65b1ad94ae6ce86ed73fc5964d65054668acb">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4d308af9802a93a0f656e8c02a3b5fcd8991407bb18360f087470db74e1f9524">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2423ef78d36e905ae993b69ff59f5df6b2e1b9492fb0fa8c6d0aad7c76d2d229">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-ef778d85ac6f9652c25cb0d631f0fe8dfb3edac4dde5d719a4fc2926fb5c3216">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c62c0139ebdb337369f4067567cd2c52b8e7decb3ddfabc77f9f67b2f6e5789c">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0b0725713b87dca1de57200214a4fe04633f0d856c39aa8032280227bf8e8141">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-f425b4378f84e0ba0c6f532facff17ff5d55b4dc6033d8bf35130a159cd2ba32">+9/-12</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>flowgger.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-af9f49f931e282dca53d1f0521b036d222fe671f77e61a876a84cf4c6d7cca4d">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c64b9ace832b8ea57a2be62f84166e03bb1904882635d444ec76a880cdf14cc0">+3/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.toml.example</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c1889866f35f98cdba9cd229fc119273c5fa5fca501451db23813b575f6fec66">+5/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e1f7c698e0e3a4e6afa971c1140e71cbf22593fbb19c81cb26b02c15c5dc46ec">+0/-25</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9edc2486fff55fc399e0ac96dba5137948a7ea7285f5ef7846835355684b7ab5">+0/-111</a>&nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4b8ec845da50cd58d011e69f9d1c30530ee1968df26616b8768bb1fc03433bbe">+0/-138</a>&nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4f5d2ea4260d490a0d6f28adde0b35eca8af77d22f3ee366a783946c53687619">+0/-25</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-bcac20d6b3cb81f0059e766839ba1ee59a885009249501b0ba1182ebb1daea25">+0/-77</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-78dc6bc53f1c760c66f43ff5f486bfe78a65bee8b2e0d4862293ec0892da2b29">+0/-123</a>&nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9c32ee8446458b6fd2ae7fee52016f4b707a59978b67888cd5bee2804d934528">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>docker-compose.elx.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9562070d7ad4a3e9b2d06567008cf35de1d96448d914b3b45bf6c36d97cdd914">+117/-0</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.spiffe.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-603fd9e7d40841d174f26b95d0cb0c9537430bf3f7a5da3ccbba4ea3d8ac66c9">+8/-158</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e45e45baeda1c1e73482975a664062aa56f20c03dd9d64a827aba57775bed0d3">+326/-269</a></td> </tr> <tr> <td><strong>Dockerfile.agent-gateway</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-332bc81a932ae08efa711a71b60fe0954d99bf17ebdab00a3baaa177a44de8b0">+94/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Dockerfile.core-elx</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-5ec7a971285669999af442a0c7f141c34f7fd9180257307f5c4ed12f789a2182">+108/-0</a>&nbsp; </td> </tr> <tr> <td><strong>Dockerfile.poller</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d3ba129830fb366bfe23b00db4ef6218b10fc981d3c04842b1b3b3b367a8982f">+0/-70</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Dockerfile.sync</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0227933b9961fd553af1d229e89d71a0271fdc475081bbcef49b587941af1eda">+0/-95</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>Dockerfile.tools</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0258db71e4070e342198965f1d046f3097640850b037df8a2287a7e239630add">+1/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>Dockerfile.web-ng</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-92d43af1965575d56c3380ecc8a81024aac2ff36f039ec2d3839e9fc7852bc10">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>agent-minimal.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-1f09fad94636c90373af8e270f6ba0332ae4f4d1df50a4909729280a3a9691e6">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>agent.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-5d33fe703515d03076d31261ecf946e9c6fc668cf5bf65099d49b670739e455e">+5/-20</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>agent.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-008f2216f159a9bd5db9cc90baaf6f1e64487df7af05b56ab3b9d6c4946aa95f">+7/-10</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>bootstrap-nested-spire.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-ab4746a08fb1e0b307a1e47660cd22182e283a087cba87dcbff0fdfe750f44f1">+0/-80</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>.gitkeep</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d72c41aab2d6f2c230a4340dfefe7917cdd12bed942c825aa0d4c9875a637bac">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>datasvc.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-3f2719d3dbfe042e8383739e3c78e74e5f851a44e5e46bea8e79c4b79fdcc34f">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>datasvc.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-3a45619e57f1e6e9a31486ec7fffb33ef246e271f82bac272ee0a946b88da70a">+14/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>db-event-writer.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9fc51271f7ef5bb460160013e24e44e829b730656891d26fc49d5fe72fbb3147">+11/-11</a>&nbsp; </td> </tr> <tr> <td><strong>db-event-writer.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-7a33f95f7545499abf0ed9fc91b58499ab209639e4885019579c959583fc7496">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>FRICTION_POINTS.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-b0653c58880f810ba832c0500733d63de309db98b43009fe73a1862494cf41bd">+0/-355</a>&nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-31849f033cfc932acee35f549c069abb1f36101c352e553dd6bff8713b29f98c">+0/-207</a>&nbsp; </td> </tr> <tr> <td><strong>SETUP_GUIDE.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-b4914f8640a78038e45f51235a624535672680dc902de5f107fc051f4f281913">+0/-307</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.edge-e2e.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-575d19ea771bdf8102cb9729db43a1bfd6afc2527160e54105beeac2e314f362">+0/-27</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>manage-packages.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-3c2ff6febbddb956c71557894adaf7d0a39a1f20dda120fe126364946bc47280">+0/-211</a>&nbsp; </td> </tr> <tr> <td><strong>setup-edge-e2e.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2714e2c7e111f69ea9e9f5ddd7f6a70fa5ea96e3a53b851cb13b8b8b7cd12917">+0/-198</a>&nbsp; </td> </tr> <tr> <td><strong>edge-poller-restart.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-96a8fe52c38fd0d7c14895127df34a27be311cac89c53d28ee178661b629bd22">+0/-178</a>&nbsp; </td> </tr> <tr> <td><strong>downstream-agent.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-747de0375ced42af978ca7dac239862bdabb7f6bd0bd634f134b485517a7b4ee">+0/-32</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>env</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-686f1a954c542f2ec9bf14c3170648b65190ad242c7f3a95a0f872ae41b8b1c6">+0/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>server.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-025f5b5ab79526cf549ca1fdb90dd659ba76b438f05a7f77d916d18728c4b572">+0/-51</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>upstream-agent.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e8a869ddf4affa31536a8d4e4e6f09c40072a7026da2c609d93c6ecf04138902">+0/-32</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>entrypoint-certs.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-83d6800b184a5233c66c69766286b0a60fece1bc64addb112d9f8dc019437f05">+13/-9</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>entrypoint-poller.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e202d27e3331088745eb55cdd2b3e40ac3f5df109d9ff5c76c0faed60772807a">+0/-274</a>&nbsp; </td> </tr> <tr> <td><strong>entrypoint-sync.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9d5620b8e6833309dbafb8ee6b6b75c3b942d163c3fe7f1a9827958b2d640265">+0/-96</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>fix-cert-permissions.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-17ea40a11edcaa7c85bb4215fda46b5a32505246fef0ab5f3ed47b28470c5ec8">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>flowgger.docker.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-824f8797b418d4b9f5ea41e4a3741a0ed64b881f343072464489a76b7ea01008">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>generate-certs.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-8298241543b4744a6ac7780c760ac5b5a0a87ba62de19c8612ebe1aba0996ebd">+214/-12</a></td> </tr> <tr> <td><strong>nats.docker.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-06f2012494f428fe1bfb304972061c2094e0d99da88ba9af6914f7776872e6eb">+16/-160</a></td> </tr> <tr> <td><strong>netflow-consumer.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-f15920e8498a24f71ce3eec4f48fe8fefbb1765a90362998af779a660fcef9e1">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.docker.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d4af38790e3657b7589cd37a7539d5308b032f11caba7aa740ddc86bf99f4415">+7/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>pg_hba.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-7bd5f7292054916c7e5997f4c84ac9ec07d4c945621a48936c2aed0575fb96eb">+9/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>pg_ident.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-e7b8ce062e32c61fdc3bcc9e525c1f1df1c8008fbc02b11409e58c67baa17cc5">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>poller-stack.compose.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-f3b5c991c2c1f7646db0ca4ed9bcb5df0f313ce6a05d8f3c890f80c873f776f5">+0/-121</a>&nbsp; </td> </tr> <tr> <td><strong>poller.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d64ebb69ec31e831efd187c47a5bfff2573960306b177f6464e91cb44a3c709d">+0/-128</a>&nbsp; </td> </tr> <tr> <td><strong>poller.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-ef5d74bb3607431245c2bf06169d7fee89cae817e114035075b59a671229ab46">+0/-135</a>&nbsp; </td> </tr> <tr> <td><strong>poller.spiffe.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4e04bd23a0216287d5c0bb3831e0f95e7922ed03e8386a10ae7f4873e4fdb538">+0/-55</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>refresh-upstream-credentials.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d3b3a8fcdea1b49c9e1c0ecc12d61fb6d416313520e8ad52edbee9094dbdc271">+0/-248</a>&nbsp; </td> </tr> <tr> <td><strong>seed-poller-kv.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c12070f475dbe7dc83e747fa6ec9d2ebdbdd97921a54f372abc89a102b783ad7">+0/-83</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>setup-edge-poller.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-d7aec89d87f4cc98f4d6935e49a8f6ce571bc6dda254d894e93b60922f3a775f">+0/-204</a>&nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-0cb49b4e37a7692f026133d5de971d449f42a1068226e848da5adf9af0ff4a2e">+5/-5</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>bootstrap-compose-spire.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-ca219a124d4c95ee7995764d7e0c322b4bfe59e357b7bcb42bc5d7c8b9b0af0d">+0/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>ssl_dist.core.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-08d49d8621b581d1a9aa5c456f61e8c5774e021083c982cbb514019f915a1701">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ssl_dist.gateway.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4a43a8290d45ac68592000e7ef51afe78b4213090155bd42aafb46e66130f7ae">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>ssl_dist.web.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-cef5be462ddb059fdfdeb9fd7c5cd70e656c4cd8b6ae1fe3fe312557b3da80ac">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>sync.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-4237fcee4f33a230abf28e12e8d4823499d163759cd1ff124fec1c62faa8b8b4">+0/-71</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>sync.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-c652c07f7127be5b2932d92e6ef4c7448c544d1f3095cb96a03294fa58fd3c4c">+0/-75</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>sysmon-osx.checker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-044334b566d907c77656b7f951092709da2a111dc968da9a76315b1c71200cf4">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>tools-profile.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-f47597e2f5d4d085d8bf109109608f8ec0b7db8e90545e869b9ae409b607a4ac">+1/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>trapd.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-1ab1a0e03e63bc02e0ef31992a7187a377927272ed2060150b40d44cc0ea3357">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>update-config.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-9ae50be83a13010a038389c74407ba1bde8cabcea0944e238c4b3374133f78bf">+1/-190</a>&nbsp; </td> </tr> <tr> <td><strong>zen-install-rules.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-74122772ee2960d407d0a0e24ac0cc9d970167ba0f51913e7edf29e092188003">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2241/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-10 07:49:23 +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/2241#issuecomment-3732116821
Original created: 2026-01-10T07:49:23Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive info in logs

Description: Debug logging prints the configured NATS credentials file path (nats.creds_file) which can
expose sensitive filesystem layout and facilitate credential targeting if logs are
accessible to untrusted parties.
setup.rs [48-53]

Referred Code
    nats.url, nats.subject, nats.stream
);
debug!("NATS timeout: {:?}", nats.timeout);
debug!("NATS creds file: {:?}", nats.creds_file);
debug!("NATS TLS cert: {:?}", nats.tls_cert);
debug!("NATS TLS key: {:?}", nats.tls_key);

Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
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: Robust Error Handling and Edge Case Management

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

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: Secure Logging Practices

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

Status:
Sensitive data in logs: New logging prints potentially sensitive configuration and identity material (e.g.,
allowed client identities and credentials/config file paths) which can leak
security-relevant details into logs.

Referred Code
	log.Printf("NATS account service starting without operator (will bootstrap later): %v", opErr)
	natsAccountServer = datasvc.NewNATSAccountServer(nil)
} else {
	natsAccountServer = datasvc.NewNATSAccountServer(operator)
	log.Printf("NATS account service initialized with operator %s", operator.Name())
}

natsAccountServer.SetAllowedClientIdentities(cfg.NATSOperator.AllowedClientIdentities)
if len(cfg.NATSOperator.AllowedClientIdentities) == 0 {
	log.Printf("Warning: no allowed client identities configured for NATS account service; requests will be rejected")
} else {
	log.Printf("NATS account service allowed identities: %v", cfg.NATSOperator.AllowedClientIdentities)
}

// Configure resolver paths for file-based JWT resolver
// Priority: environment variables > config file
operatorConfigPath := cfg.NATSOperator.OperatorConfigPath
if envPath := os.Getenv("NATS_OPERATOR_CONFIG_PATH"); envPath != "" {
	operatorConfigPath = envPath
}



 ... (clipped 30 lines)

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 context: The new NATS account service initialization/bootstrapping path logs operational messages
but does not record an auditable actor identity/outcome trail for security-relevant
actions, which may be required depending on how these endpoints are used.

Referred Code
// Initialize NATS account service
// This service is stateless - it only holds the operator key for signing operations.
// Account state (seeds, JWTs) is stored by Elixir in CNPG with AshCloak encryption.
// The service can start without an operator and bootstrap later via gRPC.
var natsAccountServer *datasvc.NATSAccountServer
if cfg.NATSOperator != nil {
	operator, opErr := accounts.NewOperator(cfg.NATSOperator)
	if opErr != nil {
		// Operator not available yet - that's okay, bootstrap will be called later
		log.Printf("NATS account service starting without operator (will bootstrap later): %v", opErr)
		natsAccountServer = datasvc.NewNATSAccountServer(nil)
	} else {
		natsAccountServer = datasvc.NewNATSAccountServer(operator)
		log.Printf("NATS account service initialized with operator %s", operator.Name())
	}

	natsAccountServer.SetAllowedClientIdentities(cfg.NATSOperator.AllowedClientIdentities)
	if len(cfg.NATSOperator.AllowedClientIdentities) == 0 {
		log.Printf("Warning: no allowed client identities configured for NATS account service; requests will be rejected")
	} else {
		log.Printf("NATS account service allowed identities: %v", cfg.NATSOperator.AllowedClientIdentities)


 ... (clipped 40 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:
Logs unvalidated details: The new request logging includes req.details directly which may contain user-controlled or
sensitive content and should be validated/redacted before logging based on the data
contract.

Referred Code
info!(
    "Received GetStatus: service_name={}, service_type={}, agent_id={}, gateway_id={}, details={}",
    req.service_name, req.service_type, req.agent_id, req.gateway_id, req.details
);

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/2241#issuecomment-3732116821 Original created: 2026-01-10T07:49:23Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/ec3b4df2e5c590d476c47b11099c837a2a840df6 --> 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>Sensitive info in logs </strong></summary><br> <b>Description:</b> Debug logging prints the configured NATS credentials file path (<code>nats.creds_file</code>) which can <br>expose sensitive filesystem layout and facilitate credential targeting if logs are <br>accessible to untrusted parties.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2241/files#diff-3891f667deb20fd26e296d3e2742c57378d3764fe1743118e612465ae360391fR48-R53'>setup.rs [48-53]</a></strong><br> <details open><summary>Referred Code</summary> ```rust nats.url, nats.subject, nats.stream ); debug!("NATS timeout: {:?}", nats.timeout); debug!("NATS creds file: {:?}", nats.creds_file); debug!("NATS TLS cert: {:?}", nats.tls_cert); debug!("NATS TLS key: {:?}", nats.tls_key); ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary>🎫 <strong>No ticket provided </strong></summary> - [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true --> </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=3>🟢</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: 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:** 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=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/2241/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R91-R141'><strong>Sensitive data in logs</strong></a>: New logging prints potentially sensitive configuration and identity material (e.g., <br>allowed client identities and credentials/config file paths) which can leak <br>security-relevant details into logs.<br> <details open><summary>Referred Code</summary> ```go log.Printf("NATS account service starting without operator (will bootstrap later): %v", opErr) natsAccountServer = datasvc.NewNATSAccountServer(nil) } else { natsAccountServer = datasvc.NewNATSAccountServer(operator) log.Printf("NATS account service initialized with operator %s", operator.Name()) } natsAccountServer.SetAllowedClientIdentities(cfg.NATSOperator.AllowedClientIdentities) if len(cfg.NATSOperator.AllowedClientIdentities) == 0 { log.Printf("Warning: no allowed client identities configured for NATS account service; requests will be rejected") } else { log.Printf("NATS account service allowed identities: %v", cfg.NATSOperator.AllowedClientIdentities) } // Configure resolver paths for file-based JWT resolver // Priority: environment variables > config file operatorConfigPath := cfg.NATSOperator.OperatorConfigPath if envPath := os.Getenv("NATS_OPERATOR_CONFIG_PATH"); envPath != "" { operatorConfigPath = envPath } ... (clipped 30 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=2>⚪</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/2241/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R82-R142'><strong>Missing audit context</strong></a>: The new NATS account service initialization/bootstrapping path logs operational messages <br>but does not record an auditable actor identity/outcome trail for security-relevant <br>actions, which may be required depending on how these endpoints are used.<br> <details open><summary>Referred Code</summary> ```go // Initialize NATS account service // This service is stateless - it only holds the operator key for signing operations. // Account state (seeds, JWTs) is stored by Elixir in CNPG with AshCloak encryption. // The service can start without an operator and bootstrap later via gRPC. var natsAccountServer *datasvc.NATSAccountServer if cfg.NATSOperator != nil { operator, opErr := accounts.NewOperator(cfg.NATSOperator) if opErr != nil { // Operator not available yet - that's okay, bootstrap will be called later log.Printf("NATS account service starting without operator (will bootstrap later): %v", opErr) natsAccountServer = datasvc.NewNATSAccountServer(nil) } else { natsAccountServer = datasvc.NewNATSAccountServer(operator) log.Printf("NATS account service initialized with operator %s", operator.Name()) } natsAccountServer.SetAllowedClientIdentities(cfg.NATSOperator.AllowedClientIdentities) if len(cfg.NATSOperator.AllowedClientIdentities) == 0 { log.Printf("Warning: no allowed client identities configured for NATS account service; requests will be rejected") } else { log.Printf("NATS account service allowed identities: %v", cfg.NATSOperator.AllowedClientIdentities) ... (clipped 40 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/2241/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33eR216-R219'><strong>Logs unvalidated details</strong></a>: The new request logging includes <code>req.details</code> directly which may contain user-controlled or <br>sensitive content and should be validated/redacted before logging based on the data <br>contract.<br> <details open><summary>Referred Code</summary> ```rust info!( "Received GetStatus: service_name={}, service_type={}, agent_id={}, gateway_id={}, details={}", req.service_name, req.service_type, req.agent_id, req.gateway_id, req.details ); ``` </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>
mfreeman451 commented 2026-01-10 07:50:37 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR review comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2241#discussion_r2678424261
Original created: 2026-01-10T07:50:37Z
Original path: docs/docs/agents.md
Original line: 171

this needs to get checked

Imported GitHub PR review comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2241#discussion_r2678424261 Original created: 2026-01-10T07:50:37Z Original path: docs/docs/agents.md Original line: 171 --- this needs to get checked
qodo-code-review[bot] commented 2026-01-10 07:50:47 +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/2241#issuecomment-3732118005
Original created: 2026-01-10T07:50:47Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect NATS wildcard matching

Fix a bug in the subject_matches function for NATS wildcard matching. Add a
check to ensure the > wildcard only matches when there is at least one token
remaining in the subject.

cmd/consumers/zen/src/config.rs [290-315]

 fn subject_matches(pattern: &str, subject: &str) -> bool {
     let pattern_tokens: Vec<&str> = pattern.split('.').collect();
     let subject_tokens: Vec<&str> = subject.split('.').collect();
 
     let mut subject_index = 0;
     for (idx, token) in pattern_tokens.iter().enumerate() {
         match *token {
-            ">" => return idx == pattern_tokens.len() - 1,
+            ">" => {
+                return idx == pattern_tokens.len() - 1 && subject_index < subject_tokens.len();
+            }
             "*" => {
                 if subject_index >= subject_tokens.len() {
                     return false;
                 }
                 subject_index += 1;
             }
             literal => {
                 if subject_index >= subject_tokens.len() || subject_tokens[subject_index] != literal
                 {
                     return false;
                 }
                 subject_index += 1;
             }
         }
     }
 
     subject_index == subject_tokens.len()
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies and fixes a bug in the NATS subject matching logic for the > wildcard, preventing incorrect matches and ensuring behavior aligns with the NATS specification.

Medium
Avoid using current time for records

Modify record_timestamp to return an error and log a warning if a timestamp is
missing from a record, instead of defaulting to the current time, to prevent
inaccurate alert evaluations.

elixir/serviceradar_core/lib/serviceradar/observability/stateful_alert_engine.ex [792-803]

 defp record_timestamp(record) do
   case Map.get(record, :time) || Map.get(record, "time") do
     %DateTime{} = dt ->
-      dt
+      {:ok, dt}
 
     _ ->
       case Map.get(record, :timestamp) || Map.get(record, "timestamp") do
-        %DateTime{} = dt -> dt
-        _ -> DateTime.utc_now()
+        %DateTime{} = dt -> {:ok, dt}
+        _ ->
+          Logger.warning("Record is missing a valid timestamp, skipping evaluation: #{inspect(record)}")
+          :error
       end
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that using DateTime.utc_now() as a fallback can cause incorrect alert evaluation for delayed events, which is a critical flaw in a stateful, time-window-based alerting system. The proposed change significantly improves the correctness and reliability of the core alerting logic.

Medium
Fix deduplication by timestamp

Correct the usage of Enum.max_by/3 in deduplicate_by_device_id/1 by providing a
mapping function to handle nil timestamps properly.

elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex [513-525]

 defp deduplicate_by_device_id(updates) do
   updates
   |> Enum.group_by(& &1.device_id)
   |> Enum.map(fn {_device_id, grouped} ->
-    # Keep the update with the latest timestamp, handling nil timestamps
-    Enum.max_by(grouped, & &1.timestamp, fn
-      nil, nil -> :eq
-      nil, _ -> :lt
-      _, nil -> :gt
-      a, b -> DateTime.compare(a, b)
+    Enum.max_by(grouped, fn upd ->
+      case upd.timestamp do
+        %DateTime{} = dt -> DateTime.to_unix(dt, :millisecond)
+        _ -> 0
+      end
     end)
   end)
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the comparator function in Enum.max_by/3 is being used incorrectly and provides a valid, idiomatic fix that correctly finds the update with the latest timestamp.

Medium
Fix race condition in shutdown

Fix a race condition in the shutdown goroutine. Remove the select block that
reads from errChan to prevent it from consuming a critical error that should be
handled by the main error path.

cmd/agent/main.go [214-218]

-			select {
-			case <-errChan:
-			case <-shutdownCtx.Done():
-				return
-			}
+			// The error from `pushLoop.Start` will be `context.Canceled` due to `cancel()`
+			// and will be handled by the main select's `err := <-errChan` case if it
+			// hasn't been handled already. We just need to wait for it to exit.
+			<-errChan
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a race condition where a critical error from the pushLoop could be consumed and ignored during shutdown, but the proposed fix is also flawed as it still consumes the error.

Medium
Use a transaction for atomicity

Wrap the database alert creation and webhook notification in an Ash.transaction
to ensure both operations succeed or fail together, preventing lost
notifications.

elixir/serviceradar_core/lib/serviceradar/monitoring/alert_generator.ex [413-436]

 defp create_alert_and_notify(attrs, opts) do
   tenant_id = Map.get(attrs, :tenant_id) || Keyword.get(opts, :tenant_id)
   tenant_schema = Keyword.get(opts, :tenant) || tenant_schema_for(tenant_id)
   actor = Keyword.get(opts, :actor) || system_actor(tenant_id)
 
   # Create the alert in the database
   if is_nil(tenant_schema) do
     Logger.warning("Skipping alert creation; tenant schema missing", tenant_id: tenant_id)
     {:error, :missing_tenant_schema}
   else
-    case Alert
-         |> Ash.Changeset.for_create(:trigger, attrs, actor: actor, tenant: tenant_schema)
-         |> Ash.create() do
-      {:ok, alert} ->
-        # Also send webhook notification
-        send_webhook_notification(alert, opts)
-        {:ok, alert}
-
-      {:error, error} ->
-        Logger.error("Failed to create alert: #{inspect(error)}")
-        {:error, error}
-    end
+    Ash.transaction(
+      fn ->
+        Alert
+        |> Ash.Changeset.for_create(:trigger, attrs, actor: actor, tenant: tenant_schema)
+        |> Ash.create!()
+        |> tap(&send_webhook_notification(&1, opts))
+      end,
+      repo: ServiceRadar.Repo,
+      tenant: tenant_schema
+    )
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential atomicity issue and proposes using a transaction, which is a valid approach to ensure the alert and notification are coupled, improving reliability.

Medium
General
Return error instead of raising exception

Change resolve_tenant to return an error tuple like {:error, :tenant_not_found}
instead of raising an ArgumentError when a tenant identifier is missing. This
allows callers to handle the error gracefully.

elixir/serviceradar_core/lib/serviceradar/edge/onboarding_packages.ex [448-461]

 defp resolve_tenant(opts, attrs \\ %{}) do
   tenant_value =
     Keyword.get(opts, :tenant) ||
       Keyword.get(opts, :tenant_id) ||
       Map.get(attrs, :tenant_id) ||
       Map.get(attrs, "tenant_id")
 
   if is_nil(tenant_value) do
-    raise ArgumentError,
-          "Tenant could not be resolved - missing tenant identifier in options or attributes"
+    {:error, :tenant_not_found}
+  else
+    case TenantSchemas.schema_for_tenant(tenant_value) do
+      nil -> {:error, :invalid_tenant}
+      schema -> {:ok, schema}
+    end
   end
-
-  TenantSchemas.schema_for_tenant(tenant_value)
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that raising an exception for a missing tenant is poor practice in a web context. Returning an error tuple allows for graceful error handling and improves the application's robustness, which is a valuable improvement.

Medium
Shuffle nodes before making RPC calls

In core_call, shuffle the list of core_nodes before iterating through them for
RPC calls. This improves resilience by distributing load and preventing repeated
calls to a single slow or unresponsive node.

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex [856-872]

 defp core_call(module, function, args, timeout \\ 5_000) do
-  nodes = core_nodes()
+  nodes = core_nodes() |> Enum.shuffle()
 
   if nodes == [] do
     {:error, :core_unavailable}
   else
     Enum.reduce_while(nodes, {:error, :core_unavailable}, fn node, _acc ->
       case :rpc.call(node, module, function, args, timeout) do
         {:badrpc, _} ->
           {:cont, {:error, :core_unavailable}}
 
         result ->
           {:halt, {:ok, result}}
       end
     end)
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion provides a valid improvement for resilience by shuffling nodes to avoid repeatedly hitting a slow or unresponsive node first. This is a good practice for distributed systems to improve load distribution and fault tolerance.

Medium
Retain previous rules on load error

In load_rules, modify the rescue block to fall back to the existing state.rules
instead of an empty list when a database error occurs. This prevents transient
errors from disabling all alerts.

elixir/serviceradar_core/lib/serviceradar/observability/stateful_alert_engine.ex [154-167]

 defp load_rules(state) do
   rules =
     StatefulAlertRule
     |> Ash.Query.for_read(:active, %{}, tenant: state.schema)
     |> Ash.read(authorize?: false)
     |> unwrap_page()
 
   updated = %{state | rules: rules, rules_loaded_at: System.monotonic_time(:millisecond)}
   {updated, rules}
 rescue
   error ->
     Logger.warning("Failed to load stateful alert rules: #{inspect(error)}")
-    {state, []}
+    {state, state.rules}
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion improves the system's resilience by preventing a transient database error from disabling all alerts. Retaining the last known set of rules ensures the alerting system remains functional during temporary outages, which is a significant reliability improvement.

Medium
Log errors for silent failures

Add logging to the error case in handle_existing_alias/4 to make failures in
DeviceAliasState.record_sighting visible for debugging.

elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex [438-473]

 defp handle_existing_alias(existing, _update, confirm_threshold, actor) do
   alias ServiceRadar.Identity.DeviceAliasState
 
   # Record the sighting
   case DeviceAliasState.record_sighting(
          existing,
          %{confirm_threshold: confirm_threshold},
          actor: actor
        ) do
     {:ok, updated} ->
       # Generate event if state changed
       if updated.state != existing.state do
         %{
           device_id: existing.device_id,
           partition: existing.partition || "",
           action: "alias_state_changed",
           reason: "state_transition",
           timestamp: DateTime.utc_now(),
           severity: "Info",
           level: 6,
           metadata: %{
             "alias_type" => to_string(existing.alias_type),
             "alias_value" => existing.alias_value,
             "previous_state" => to_string(existing.state),
             "new_state" => to_string(updated.state),
             "sighting_count" => to_string(updated.sighting_count)
           }
         }
       else
         nil
       end
 
-    {:error, _reason} ->
+    {:error, reason} ->
+      Logger.error("Failed to record alias sighting: #{inspect(reason)}")
       nil
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that an error from DeviceAliasState.record_sighting is silently ignored, and adding logging would improve observability and debugging for this failure case.

Medium
Cache loaded root CA for performance

Cache the root CA certificate and key using :persistent_term in load_root_ca/0
to avoid repeated file I/O and improve performance.

elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [284-298]

 defp load_root_ca do
-  config = Application.get_env(:serviceradar_core, :root_ca, [])
-  cert_file = Keyword.get(config, :cert_file, "/etc/serviceradar/certs/root.pem")
-  key_file = Keyword.get(config, :key_file, "/etc/serviceradar/certs/root-key.pem")
+  case :persistent_term.get(__MODULE__, :not_found) do
+    :not_found ->
+      config = Application.get_env(:serviceradar_core, :root_ca, [])
+      cert_file = Keyword.get(config, :cert_file, "/etc/serviceradar/certs/root.pem")
+      key_file = Keyword.get(config, :key_file, "/etc/serviceradar/certs/root-key.pem")
 
-  with {:ok, cert_pem} <- File.read(cert_file),
-       {:ok, key_pem} <- File.read(key_file),
-       {:ok, cert} <- decode_pem_cert(cert_pem),
-       {:ok, key} <- decode_pem_key(key_pem) do
-    {:ok, {cert, key}}
-  else
-    {:error, :enoent} -> {:error, :root_ca_not_found}
-    error -> error
+      with {:ok, cert_pem} <- File.read(cert_file),
+           {:ok, key_pem} <- File.read(key_file),
+           {:ok, cert} <- decode_pem_cert(cert_pem),
+           {:ok, key} <- decode_pem_key(key_pem) do
+        :persistent_term.put(__MODULE__, {:ok, {cert, key}})
+        {:ok, {cert, key}}
+      else
+        {:error, :enoent} -> {:error, :root_ca_not_found}
+        error -> error
+      end
+
+    result ->
+      result
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion provides a valid performance optimization by caching file reads using :persistent_term, which is a good pattern for frequently accessed, rarely changing configuration data.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2241#issuecomment-3732118005 Original created: 2026-01-10T07:50:47Z --- ## PR Code Suggestions ✨ <!-- ec3b4df --> 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=5>Possible issue</td> <td> <details><summary>Fix incorrect NATS wildcard matching</summary> ___ **Fix a bug in the <code>subject_matches</code> function for NATS wildcard matching. Add a <br>check to ensure the <code>></code> wildcard only matches when there is at least one token <br>remaining in the subject.** [cmd/consumers/zen/src/config.rs [290-315]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-05038f3867985e757de9027609950e682bad6d1992dac6acd7c28962a3c65dc4R290-R315) ```diff fn subject_matches(pattern: &str, subject: &str) -> bool { let pattern_tokens: Vec<&str> = pattern.split('.').collect(); let subject_tokens: Vec<&str> = subject.split('.').collect(); let mut subject_index = 0; for (idx, token) in pattern_tokens.iter().enumerate() { match *token { - ">" => return idx == pattern_tokens.len() - 1, + ">" => { + return idx == pattern_tokens.len() - 1 && subject_index < subject_tokens.len(); + } "*" => { if subject_index >= subject_tokens.len() { return false; } subject_index += 1; } literal => { if subject_index >= subject_tokens.len() || subject_tokens[subject_index] != literal { return false; } subject_index += 1; } } } subject_index == subject_tokens.len() } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=0 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies and fixes a bug in the NATS subject matching logic for the `>` wildcard, preventing incorrect matches and ensuring behavior aligns with the NATS specification. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Avoid using current time for records</summary> ___ **Modify <code>record_timestamp</code> to return an error and log a warning if a timestamp is <br>missing from a record, instead of defaulting to the current time, to prevent <br>inaccurate alert evaluations.** [elixir/serviceradar_core/lib/serviceradar/observability/stateful_alert_engine.ex [792-803]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-bae3a52db882de8c947e62f219a95dff8db4e155e37d9a361dbe14ec25fcd3bdR792-R803) ```diff defp record_timestamp(record) do case Map.get(record, :time) || Map.get(record, "time") do %DateTime{} = dt -> - dt + {:ok, dt} _ -> case Map.get(record, :timestamp) || Map.get(record, "timestamp") do - %DateTime{} = dt -> dt - _ -> DateTime.utc_now() + %DateTime{} = dt -> {:ok, dt} + _ -> + Logger.warning("Record is missing a valid timestamp, skipping evaluation: #{inspect(record)}") + :error end end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies that using `DateTime.utc_now()` as a fallback can cause incorrect alert evaluation for delayed events, which is a critical flaw in a stateful, time-window-based alerting system. The proposed change significantly improves the correctness and reliability of the core alerting logic. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Fix deduplication by timestamp</summary> ___ **Correct the usage of <code>Enum.max_by/3</code> in <code>deduplicate_by_device_id/1</code> by providing a <br>mapping function to handle <code>nil</code> timestamps properly.** [elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex [513-525]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-bc3743067ea774f59bc5665770f7110a2d6e90f6e1156a7717a1c287f8979d28R513-R525) ```diff defp deduplicate_by_device_id(updates) do updates |> Enum.group_by(& &1.device_id) |> Enum.map(fn {_device_id, grouped} -> - # Keep the update with the latest timestamp, handling nil timestamps - Enum.max_by(grouped, & &1.timestamp, fn - nil, nil -> :eq - nil, _ -> :lt - _, nil -> :gt - a, b -> DateTime.compare(a, b) + Enum.max_by(grouped, fn upd -> + case upd.timestamp do + %DateTime{} = dt -> DateTime.to_unix(dt, :millisecond) + _ -> 0 + end end) end) end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies that the comparator function in `Enum.max_by/3` is being used incorrectly and provides a valid, idiomatic fix that correctly finds the update with the latest timestamp. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Fix race condition in shutdown</summary> ___ **Fix a race condition in the shutdown goroutine. Remove the <code>select</code> block that <br>reads from <code>errChan</code> to prevent it from consuming a critical error that should be <br>handled by the main error path.** [cmd/agent/main.go [214-218]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3R214-R218) ```diff - select { - case <-errChan: - case <-shutdownCtx.Done(): - return - } + // The error from `pushLoop.Start` will be `context.Canceled` due to `cancel()` + // and will be handled by the main select's `err := <-errChan` case if it + // hasn't been handled already. We just need to wait for it to exit. + <-errChan ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=3 --> <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly identifies a race condition where a critical error from the `pushLoop` could be consumed and ignored during shutdown, but the proposed fix is also flawed as it still consumes the error. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Use a transaction for atomicity</summary> ___ **Wrap the database alert creation and webhook notification in an <code>Ash.transaction</code> <br>to ensure both operations succeed or fail together, preventing lost <br>notifications.** [elixir/serviceradar_core/lib/serviceradar/monitoring/alert_generator.ex [413-436]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-62074160ac91002a439bab337a032329681bc55c84a59ab9934bc76d05a5de04R413-R436) ```diff defp create_alert_and_notify(attrs, opts) do tenant_id = Map.get(attrs, :tenant_id) || Keyword.get(opts, :tenant_id) tenant_schema = Keyword.get(opts, :tenant) || tenant_schema_for(tenant_id) actor = Keyword.get(opts, :actor) || system_actor(tenant_id) # Create the alert in the database if is_nil(tenant_schema) do Logger.warning("Skipping alert creation; tenant schema missing", tenant_id: tenant_id) {:error, :missing_tenant_schema} else - case Alert - |> Ash.Changeset.for_create(:trigger, attrs, actor: actor, tenant: tenant_schema) - |> Ash.create() do - {:ok, alert} -> - # Also send webhook notification - send_webhook_notification(alert, opts) - {:ok, alert} - - {:error, error} -> - Logger.error("Failed to create alert: #{inspect(error)}") - {:error, error} - end + Ash.transaction( + fn -> + Alert + |> Ash.Changeset.for_create(:trigger, attrs, actor: actor, tenant: tenant_schema) + |> Ash.create!() + |> tap(&send_webhook_notification(&1, opts)) + end, + repo: ServiceRadar.Repo, + tenant: tenant_schema + ) end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly identifies a potential atomicity issue and proposes using a transaction, which is a valid approach to ensure the alert and notification are coupled, improving reliability. </details></details></td><td align=center>Medium </td></tr><tr><td rowspan=5>General</td> <td> <details><summary>Return error instead of raising exception</summary> ___ **Change <code>resolve_tenant</code> to return an error tuple like <code>{:error, :tenant_not_found}</code> <br>instead of raising an <code>ArgumentError</code> when a tenant identifier is missing. This <br>allows callers to handle the error gracefully.** [elixir/serviceradar_core/lib/serviceradar/edge/onboarding_packages.ex [448-461]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-e4fe8e19bc324416302bb4c962f57133b3f62eb82053766844d881c522a473e5R448-R461) ```diff defp resolve_tenant(opts, attrs \\ %{}) do tenant_value = Keyword.get(opts, :tenant) || Keyword.get(opts, :tenant_id) || Map.get(attrs, :tenant_id) || Map.get(attrs, "tenant_id") if is_nil(tenant_value) do - raise ArgumentError, - "Tenant could not be resolved - missing tenant identifier in options or attributes" + {:error, :tenant_not_found} + else + case TenantSchemas.schema_for_tenant(tenant_value) do + nil -> {:error, :invalid_tenant} + schema -> {:ok, schema} + end end - - TenantSchemas.schema_for_tenant(tenant_value) end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly points out that raising an exception for a missing tenant is poor practice in a web context. Returning an error tuple allows for graceful error handling and improves the application's robustness, which is a valuable improvement. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Shuffle nodes before making RPC calls</summary> ___ **In <code>core_call</code>, shuffle the list of <code>core_nodes</code> before iterating through them for <br>RPC calls. This improves resilience by distributing load and preventing repeated <br>calls to a single slow or unresponsive node.** [elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex [856-872]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-369a368073dc8ec1140bcea699005a1ce97a90cd59629df0bd18c71c7ffaae9fR856-R872) ```diff defp core_call(module, function, args, timeout \\ 5_000) do - nodes = core_nodes() + nodes = core_nodes() |> Enum.shuffle() if nodes == [] do {:error, :core_unavailable} else Enum.reduce_while(nodes, {:error, :core_unavailable}, fn node, _acc -> case :rpc.call(node, module, function, args, timeout) do {:badrpc, _} -> {:cont, {:error, :core_unavailable}} result -> {:halt, {:ok, result}} end end) end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion provides a valid improvement for resilience by shuffling nodes to avoid repeatedly hitting a slow or unresponsive node first. This is a good practice for distributed systems to improve load distribution and fault tolerance. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Retain previous rules on load error</summary> ___ **In <code>load_rules</code>, modify the <code>rescue</code> block to fall back to the existing <code>state.rules</code> <br>instead of an empty list when a database error occurs. This prevents transient <br>errors from disabling all alerts.** [elixir/serviceradar_core/lib/serviceradar/observability/stateful_alert_engine.ex [154-167]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-bae3a52db882de8c947e62f219a95dff8db4e155e37d9a361dbe14ec25fcd3bdR154-R167) ```diff defp load_rules(state) do rules = StatefulAlertRule |> Ash.Query.for_read(:active, %{}, tenant: state.schema) |> Ash.read(authorize?: false) |> unwrap_page() updated = %{state | rules: rules, rules_loaded_at: System.monotonic_time(:millisecond)} {updated, rules} rescue error -> Logger.warning("Failed to load stateful alert rules: #{inspect(error)}") - {state, []} + {state, state.rules} end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion improves the system's resilience by preventing a transient database error from disabling all alerts. Retaining the last known set of rules ensures the alerting system remains functional during temporary outages, which is a significant reliability improvement. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Log errors for silent failures</summary> ___ **Add logging to the error case in <code>handle_existing_alias/4</code> to make failures in <br><code>DeviceAliasState.record_sighting</code> visible for debugging.** [elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex [438-473]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-bc3743067ea774f59bc5665770f7110a2d6e90f6e1156a7717a1c287f8979d28R438-R473) ```diff defp handle_existing_alias(existing, _update, confirm_threshold, actor) do alias ServiceRadar.Identity.DeviceAliasState # Record the sighting case DeviceAliasState.record_sighting( existing, %{confirm_threshold: confirm_threshold}, actor: actor ) do {:ok, updated} -> # Generate event if state changed if updated.state != existing.state do %{ device_id: existing.device_id, partition: existing.partition || "", action: "alias_state_changed", reason: "state_transition", timestamp: DateTime.utc_now(), severity: "Info", level: 6, metadata: %{ "alias_type" => to_string(existing.alias_type), "alias_value" => existing.alias_value, "previous_state" => to_string(existing.state), "new_state" => to_string(updated.state), "sighting_count" => to_string(updated.sighting_count) } } else nil end - {:error, _reason} -> + {:error, reason} -> + Logger.error("Failed to record alias sighting: #{inspect(reason)}") nil end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly points out that an error from `DeviceAliasState.record_sighting` is silently ignored, and adding logging would improve observability and debugging for this failure case. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Cache loaded root CA for performance</summary> ___ **Cache the root CA certificate and key using <code>:persistent_term</code> in <code>load_root_ca/0</code> <br>to avoid repeated file I/O and improve performance.** [elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [284-298]](https://github.com/carverauto/serviceradar/pull/2241/files#diff-b48e4a9e1189da61e2a60e16f56fce81298d76b7cdab745107140fed3f6e48b4R284-R298) ```diff defp load_root_ca do - config = Application.get_env(:serviceradar_core, :root_ca, []) - cert_file = Keyword.get(config, :cert_file, "/etc/serviceradar/certs/root.pem") - key_file = Keyword.get(config, :key_file, "/etc/serviceradar/certs/root-key.pem") + case :persistent_term.get(__MODULE__, :not_found) do + :not_found -> + config = Application.get_env(:serviceradar_core, :root_ca, []) + cert_file = Keyword.get(config, :cert_file, "/etc/serviceradar/certs/root.pem") + key_file = Keyword.get(config, :key_file, "/etc/serviceradar/certs/root-key.pem") - with {:ok, cert_pem} <- File.read(cert_file), - {:ok, key_pem} <- File.read(key_file), - {:ok, cert} <- decode_pem_cert(cert_pem), - {:ok, key} <- decode_pem_key(key_pem) do - {:ok, {cert, key}} - else - {:error, :enoent} -> {:error, :root_ca_not_found} - error -> error + with {:ok, cert_pem} <- File.read(cert_file), + {:ok, key_pem} <- File.read(key_file), + {:ok, cert} <- decode_pem_cert(cert_pem), + {:ok, key} <- decode_pem_key(key_pem) do + :persistent_term.put(__MODULE__, {:ok, {cert, key}}) + {:ok, {cert, key}} + else + {:error, :enoent} -> {:error, :root_ca_not_found} + error -> error + end + + result -> + result end end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion provides a valid performance optimization by caching file reads using `:persistent_term`, which is a good pattern for frequently accessed, rarely changing configuration data. </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>
mfreeman451 commented 2026-01-10 07:53:23 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR review comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2241#discussion_r2678425501
Original created: 2026-01-10T07:53:23Z
Original path: elixir/serviceradar_core/lib/serviceradar/event_writer/pipeline.ex
Original line: 83

is this actually being used anymore? shouldnt this all be replaced by db-event-writer?

Imported GitHub PR review comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2241#discussion_r2678425501 Original created: 2026-01-10T07:53:23Z Original path: elixir/serviceradar_core/lib/serviceradar/event_writer/pipeline.ex Original line: 83 --- is this actually being used anymore? shouldnt this all be replaced by db-event-writer?
qodo-code-review[bot] commented 2026-01-10 20:01:15 +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/2241#issuecomment-3733444870
Original created: 2026-01-10T20:01:15Z

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: test-rust (rust/srql)

Failed stage: Run Tests []

Failed test name: srql_api_queries

Failure summary:

The action failed because the integration test srql_api_queries crashed while trying to connect to
the remote PostgreSQL fixture.
- The panic occurred in rust/srql/tests/support/harness.rs:74:10 when
the test tried to acquire the remote fixture lock and received a database error.
- The underlying
cause is a PostgreSQL access policy/TLS mismatch: FATAL: pg_hba.conf rejects connection for host
"10.42.68.65", user "srql_hydra", database "postgres", no encryption.
This indicates the DB
server’s pg_hba.conf does not allow connections from the GitHub runner IP without encryption (or
does not allow that host/user/db combination at all), so the test cannot proceed.

Relevant error logs:
1:  Runner name: 'arc-runner-set-hk6mk-runner-b62bm'
2:  Runner group name: 'Default'
...

166:  ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m
167:  ^[[36;1m  sudo apt-get update^[[0m
168:  ^[[36;1m  sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m
169:  ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m
170:  ^[[36;1m  sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
171:  ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m
172:  ^[[36;1m  sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
173:  ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m
174:  ^[[36;1m  sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
175:  ^[[36;1melse^[[0m
176:  ^[[36;1m  echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m
177:  ^[[36;1m  exit 1^[[0m
178:  ^[[36;1mfi^[[0m
179:  ^[[36;1m^[[0m
180:  ^[[36;1mensure_pkg_config^[[0m
181:  ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m
182:  shell: /usr/bin/bash -e {0}
...

221:  libprotoc 3.12.4
222:  ##[group]Run bazelbuild/setup-bazelisk@v3
223:  with:
224:  bazelisk-version: 1.x
225:  token: ***
226:  env:
227:  BUILDBUDDY_ORG_API_KEY: ***
228:  SRQL_TEST_DATABASE_URL: ***
229:  SRQL_TEST_ADMIN_URL: ***
230:  ##[endgroup]
231:  Attempting to download 1.x...
232:  Acquiring v1.27.0 from https://github.com/bazelbuild/bazelisk/releases/download/v1.27.0/bazelisk-linux-amd64
233:  Adding to the cache ...
234:  Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.27.0/x64
235:  Added bazelisk to the path
236:  ##[warning]Failed to restore: Cache service responded with 400
237:  Restored bazelisk cache dir @ /home/runner/.cache/bazelisk
...

293:  targets: 
294:  components: clippy
295:  ##[endgroup]
296:  ##[group]Run : set $CARGO_HOME
297:  ^[[36;1m: set $CARGO_HOME^[[0m
298:  ^[[36;1mecho CARGO_HOME=${CARGO_HOME:-"$HOME/.cargo"} >> $GITHUB_ENV^[[0m
299:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
300:  env:
301:  BUILDBUDDY_ORG_API_KEY: ***
302:  SRQL_TEST_DATABASE_URL: ***
303:  SRQL_TEST_ADMIN_URL: ***
304:  ##[endgroup]
305:  ##[group]Run : install rustup if needed
306:  ^[[36;1m: install rustup if needed^[[0m
307:  ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m
308:  ^[[36;1m  curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m
309:  ^[[36;1m  echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m
...

409:  ^[[36;1m    echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse >> $GITHUB_ENV^[[0m
410:  ^[[36;1m  elif rustc +stable --version --verbose | grep -q '^release: 1\.6[67]\.'; then^[[0m
411:  ^[[36;1m    touch "/home/runner/_work/_temp"/.implicit_cargo_registries_crates_io_protocol || true^[[0m
412:  ^[[36;1m    echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git >> $GITHUB_ENV^[[0m
413:  ^[[36;1m  fi^[[0m
414:  ^[[36;1mfi^[[0m
415:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
416:  env:
417:  BUILDBUDDY_ORG_API_KEY: ***
418:  SRQL_TEST_DATABASE_URL: ***
419:  SRQL_TEST_ADMIN_URL: ***
420:  CARGO_HOME: /home/runner/.cargo
421:  CARGO_INCREMENTAL: 0
422:  CARGO_TERM_COLOR: always
423:  ##[endgroup]
424:  ##[group]Run : work around spurious network errors in curl 8.0
425:  ^[[36;1m: work around spurious network errors in curl 8.0^[[0m
426:  ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m
...

584:  ^[[1m^[[92m  Downloaded^[[0m tokio-stream v0.1.18
585:  ^[[1m^[[92m  Downloaded^[[0m tokio-rustls v0.26.4
586:  ^[[1m^[[92m  Downloaded^[[0m tinyvec v1.10.0
587:  ^[[1m^[[92m  Downloaded^[[0m rustls-webpki v0.101.7
588:  ^[[1m^[[92m  Downloaded^[[0m tracing-log v0.2.0
589:  ^[[1m^[[92m  Downloaded^[[0m tower-service v0.3.3
590:  ^[[1m^[[92m  Downloaded^[[0m tower-layer v0.3.3
591:  ^[[1m^[[92m  Downloaded^[[0m tonic-prost-build v0.14.2
592:  ^[[1m^[[92m  Downloaded^[[0m logos v0.15.1
593:  ^[[1m^[[92m  Downloaded^[[0m tonic-prost v0.14.2
594:  ^[[1m^[[92m  Downloaded^[[0m ring v0.17.14
595:  ^[[1m^[[92m  Downloaded^[[0m tonic-build v0.14.2
596:  ^[[1m^[[92m  Downloaded^[[0m toml_write v0.1.2
597:  ^[[1m^[[92m  Downloaded^[[0m toml_datetime v0.6.11
598:  ^[[1m^[[92m  Downloaded^[[0m tokio-postgres-rustls v0.13.0
599:  ^[[1m^[[92m  Downloaded^[[0m thiserror-impl v2.0.17
600:  ^[[1m^[[92m  Downloaded^[[0m thiserror-impl v1.0.69
601:  ^[[1m^[[92m  Downloaded^[[0m spiffe v0.6.7
602:  ^[[1m^[[92m  Downloaded^[[0m serde_json v1.0.149
603:  ^[[1m^[[92m  Downloaded^[[0m tokio-macros v2.6.0
604:  ^[[1m^[[92m  Downloaded^[[0m tls_codec_derive v0.4.2
605:  ^[[1m^[[92m  Downloaded^[[0m tls_codec v0.4.2
606:  ^[[1m^[[92m  Downloaded^[[0m tinyvec_macros v0.1.1
607:  ^[[1m^[[92m  Downloaded^[[0m tinystr v0.8.2
608:  ^[[1m^[[92m  Downloaded^[[0m time-macros v0.2.24
609:  ^[[1m^[[92m  Downloaded^[[0m time-core v0.1.6
610:  ^[[1m^[[92m  Downloaded^[[0m thread_local v1.1.9
611:  ^[[1m^[[92m  Downloaded^[[0m thiserror v2.0.17
612:  ^[[1m^[[92m  Downloaded^[[0m thiserror v1.0.69
613:  ^[[1m^[[92m  Downloaded^[[0m tempfile v3.24.0
...

625:  ^[[1m^[[92m  Downloaded^[[0m spki v0.7.3
626:  ^[[1m^[[92m  Downloaded^[[0m slab v0.4.11
627:  ^[[1m^[[92m  Downloaded^[[0m siphasher v1.0.1
628:  ^[[1m^[[92m  Downloaded^[[0m simple_asn1 v0.6.3
629:  ^[[1m^[[92m  Downloaded^[[0m signal-hook-registry v1.4.8
630:  ^[[1m^[[92m  Downloaded^[[0m shlex v1.3.0
631:  ^[[1m^[[92m  Downloaded^[[0m sharded-slab v0.1.7
632:  ^[[1m^[[92m  Downloaded^[[0m serde_with_macros v3.16.1
633:  ^[[1m^[[92m  Downloaded^[[0m serde_derive v1.0.228
634:  ^[[1m^[[92m  Downloaded^[[0m serde v1.0.228
635:  ^[[1m^[[92m  Downloaded^[[0m schemars v1.2.0
636:  ^[[1m^[[92m  Downloaded^[[0m schemars v0.9.0
637:  ^[[1m^[[92m  Downloaded^[[0m rustls-webpki v0.103.8
638:  ^[[1m^[[92m  Downloaded^[[0m rand v0.9.2
639:  ^[[1m^[[92m  Downloaded^[[0m prost-reflect v0.16.3
640:  ^[[1m^[[92m  Downloaded^[[0m serde_path_to_error v0.1.20
641:  ^[[1m^[[92m  Downloaded^[[0m semver v1.0.27
...

812:  ^[[1m^[[92m    Checking^[[0m scopeguard v1.2.0
813:  ^[[1m^[[92m   Compiling^[[0m serde_core v1.0.228
814:  ^[[1m^[[92m    Checking^[[0m futures-sink v0.3.31
815:  ^[[1m^[[92m    Checking^[[0m once_cell v1.21.3
816:  ^[[1m^[[92m   Compiling^[[0m autocfg v1.5.0
817:  ^[[1m^[[92m    Checking^[[0m memchr v2.7.6
818:  ^[[1m^[[92m   Compiling^[[0m fnv v1.0.7
819:  ^[[1m^[[92m    Checking^[[0m pin-utils v0.1.0
820:  ^[[1m^[[92m    Checking^[[0m futures-task v0.3.31
821:  ^[[1m^[[92m   Compiling^[[0m regex-syntax v0.8.8
822:  ^[[1m^[[92m   Compiling^[[0m serde v1.0.228
823:  ^[[1m^[[92m    Checking^[[0m equivalent v1.0.2
824:  ^[[1m^[[92m   Compiling^[[0m zerocopy v0.8.33
825:  ^[[1m^[[92m    Checking^[[0m hashbrown v0.16.1
826:  ^[[1m^[[92m    Checking^[[0m stable_deref_trait v1.2.1
827:  ^[[1m^[[92m   Compiling^[[0m thiserror v2.0.17
828:  ^[[1m^[[92m   Compiling^[[0m heck v0.5.0
...

902:  ^[[1m^[[92m    Checking^[[0m matchit v0.8.4
903:  ^[[1m^[[92m   Compiling^[[0m oid-registry v0.8.1
904:  ^[[1m^[[92m    Checking^[[0m fallible-iterator v0.2.0
905:  ^[[1m^[[92m   Compiling^[[0m logos-codegen v0.15.1
906:  ^[[1m^[[92m   Compiling^[[0m pq-sys v0.7.5
907:  ^[[1m^[[92m    Checking^[[0m num-integer v0.1.46
908:  ^[[1m^[[92m    Checking^[[0m block-buffer v0.10.4
909:  ^[[1m^[[92m    Checking^[[0m crypto-common v0.1.7
910:  ^[[1m^[[92m    Checking^[[0m hashbrown v0.12.3
911:  ^[[1m^[[92m    Checking^[[0m utf8_iter v1.0.4
912:  ^[[1m^[[92m    Checking^[[0m ryu v1.0.22
913:  ^[[1m^[[92m   Compiling^[[0m diesel_derives v2.3.6
914:  ^[[1m^[[92m    Checking^[[0m phf_shared v0.13.1
915:  ^[[1m^[[92m    Checking^[[0m pem v3.0.6
916:  ^[[1m^[[92m    Checking^[[0m num-bigint v0.4.6
917:  ^[[1m^[[92m   Compiling^[[0m thiserror v1.0.69
918:  ^[[1m^[[92m    Checking^[[0m unicode-normalization v0.1.25
...

945:  ^[[1m^[[92m   Compiling^[[0m synstructure v0.13.2
946:  ^[[1m^[[92m   Compiling^[[0m darling_core v0.21.3
947:  ^[[1m^[[92m   Compiling^[[0m diesel_table_macro_syntax v0.3.0
948:  ^[[1m^[[92m   Compiling^[[0m pulldown-cmark-to-cmark v21.1.0
949:  ^[[1m^[[92m    Checking^[[0m time v0.3.44
950:  ^[[1m^[[92m    Checking^[[0m http-body v1.0.1
951:  ^[[1m^[[92m    Checking^[[0m http-body-util v0.1.3
952:  ^[[1m^[[92m   Compiling^[[0m tonic-build v0.14.2
953:  ^[[1m^[[92m    Checking^[[0m axum-core v0.5.6
954:  ^[[1m^[[92m   Compiling^[[0m tokio-macros v2.6.0
955:  ^[[1m^[[92m   Compiling^[[0m tracing-attributes v0.1.31
956:  ^[[1m^[[92m   Compiling^[[0m zerofrom-derive v0.1.6
957:  ^[[1m^[[92m   Compiling^[[0m displaydoc v0.2.5
958:  ^[[1m^[[92m   Compiling^[[0m yoke-derive v0.8.1
959:  ^[[1m^[[92m   Compiling^[[0m serde_derive v1.0.228
960:  ^[[1m^[[92m   Compiling^[[0m thiserror-impl v2.0.17
961:  ^[[1m^[[92m   Compiling^[[0m prost-derive v0.14.1
962:  ^[[1m^[[92m   Compiling^[[0m zerovec-derive v0.11.2
963:  ^[[1m^[[92m   Compiling^[[0m async-trait v0.1.89
964:  ^[[1m^[[92m   Compiling^[[0m zeroize_derive v1.4.3
965:  ^[[1m^[[92m   Compiling^[[0m prost-derive v0.13.5
966:  ^[[1m^[[92m   Compiling^[[0m miette-derive v7.6.0
967:  ^[[1m^[[92m   Compiling^[[0m der_derive v0.7.3
968:  ^[[1m^[[92m   Compiling^[[0m pin-project-internal v1.1.10
969:  ^[[1m^[[92m   Compiling^[[0m asn1-rs-impl v0.2.0
970:  ^[[1m^[[92m   Compiling^[[0m asn1-rs-derive v0.6.0
971:  ^[[1m^[[92m   Compiling^[[0m axum-macros v0.4.2
972:  ^[[1m^[[92m   Compiling^[[0m async-stream-impl v0.3.6
973:  ^[[1m^[[92m   Compiling^[[0m thiserror-impl v1.0.69
974:  ^[[1m^[[92m    Checking^[[0m errno v0.3.14
...

976:  ^[[1m^[[92m    Checking^[[0m socket2 v0.6.1
977:  ^[[1m^[[92m    Checking^[[0m parking_lot v0.12.5
978:  ^[[1m^[[92m    Checking^[[0m mio v1.1.1
979:  ^[[1m^[[92m    Checking^[[0m getrandom v0.2.16
980:  ^[[1m^[[92m    Checking^[[0m zeroize v1.8.2
981:  ^[[1m^[[92m    Checking^[[0m socket2 v0.5.10
982:  ^[[1m^[[92m    Checking^[[0m rand_core v0.6.4
983:  ^[[1m^[[92m    Checking^[[0m rustls-pki-types v1.13.2
984:  ^[[1m^[[92m    Checking^[[0m rand_core v0.9.3
985:  ^[[1m^[[92m    Checking^[[0m tokio v1.49.0
986:  ^[[1m^[[92m    Checking^[[0m rand_chacha v0.3.1
987:  ^[[1m^[[92m    Checking^[[0m async-stream v0.3.6
988:  ^[[1m^[[92m    Checking^[[0m rand_chacha v0.9.0
989:  ^[[1m^[[92m    Checking^[[0m rustls-pemfile v2.2.0
990:  ^[[1m^[[92m   Compiling^[[0m logos-derive v0.15.1
991:  ^[[1m^[[92m    Checking^[[0m serde_path_to_error v0.1.20
992:  ^[[1m^[[92m    Checking^[[0m rand v0.9.2
...

1116:  ^[[1m^[[92m   Compiling^[[0m futures-task v0.3.31
1117:  ^[[1m^[[92m   Compiling^[[0m stable_deref_trait v1.2.1
1118:  ^[[1m^[[92m   Compiling^[[0m libc v0.2.180
1119:  ^[[1m^[[92m   Compiling^[[0m anyhow v1.0.100
1120:  ^[[1m^[[92m   Compiling^[[0m serde_core v1.0.228
1121:  ^[[1m^[[92m   Compiling^[[0m tower-service v0.3.3
1122:  ^[[1m^[[92m   Compiling^[[0m zerocopy v0.8.33
1123:  ^[[1m^[[92m   Compiling^[[0m percent-encoding v2.3.2
1124:  ^[[1m^[[92m   Compiling^[[0m lock_api v0.4.14
1125:  ^[[1m^[[92m   Compiling^[[0m num-traits v0.2.19
1126:  ^[[1m^[[92m   Compiling^[[0m lazy_static v1.5.0
1127:  ^[[1m^[[92m   Compiling^[[0m subtle v2.6.1
1128:  ^[[1m^[[92m   Compiling^[[0m typenum v1.19.0
1129:  ^[[1m^[[92m   Compiling^[[0m futures-channel v0.3.31
1130:  ^[[1m^[[92m   Compiling^[[0m base64 v0.22.1
1131:  ^[[1m^[[92m   Compiling^[[0m thiserror v2.0.17
1132:  ^[[1m^[[92m   Compiling^[[0m untrusted v0.9.0
...

1196:  ^[[1m^[[92m   Compiling^[[0m generic-array v0.14.7
1197:  ^[[1m^[[92m   Compiling^[[0m phf v0.13.1
1198:  ^[[1m^[[92m   Compiling^[[0m bitflags v2.10.0
1199:  ^[[1m^[[92m   Compiling^[[0m iana-time-zone v0.1.64
1200:  ^[[1m^[[92m   Compiling^[[0m regex-syntax v0.8.8
1201:  ^[[1m^[[92m   Compiling^[[0m winnow v0.7.14
1202:  ^[[1m^[[92m   Compiling^[[0m tinystr v0.8.2
1203:  ^[[1m^[[92m   Compiling^[[0m potential_utf v0.1.4
1204:  ^[[1m^[[92m   Compiling^[[0m async-stream v0.3.6
1205:  ^[[1m^[[92m   Compiling^[[0m rustls-pemfile v2.2.0
1206:  ^[[1m^[[92m   Compiling^[[0m icu_collections v2.1.1
1207:  ^[[1m^[[92m   Compiling^[[0m num-bigint v0.4.6
1208:  ^[[1m^[[92m   Compiling^[[0m downcast-rs v2.0.2
1209:  ^[[1m^[[92m   Compiling^[[0m stringprep v0.1.5
1210:  ^[[1m^[[92m   Compiling^[[0m icu_locale_core v2.1.1
1211:  ^[[1m^[[92m   Compiling^[[0m thiserror v1.0.69
1212:  ^[[1m^[[92m   Compiling^[[0m tracing-log v0.2.0
...

1257:  ^[[1m^[[92m   Compiling^[[0m rand_chacha v0.9.0
1258:  ^[[1m^[[92m   Compiling^[[0m icu_properties v2.1.2
1259:  ^[[1m^[[92m   Compiling^[[0m logos v0.15.1
1260:  ^[[1m^[[92m   Compiling^[[0m icu_normalizer v2.1.1
1261:  ^[[1m^[[92m   Compiling^[[0m rand_chacha v0.3.1
1262:  ^[[1m^[[92m   Compiling^[[0m spki v0.7.3
1263:  ^[[1m^[[92m   Compiling^[[0m rusticata-macros v4.1.0
1264:  ^[[1m^[[92m   Compiling^[[0m rand v0.9.2
1265:  ^[[1m^[[92m   Compiling^[[0m pkcs8 v0.10.2
1266:  ^[[1m^[[92m   Compiling^[[0m x509-cert v0.2.5
1267:  ^[[1m^[[92m   Compiling^[[0m prost-types v0.13.5
1268:  ^[[1m^[[92m   Compiling^[[0m rand v0.8.5
1269:  ^[[1m^[[92m   Compiling^[[0m prost-types v0.14.1
1270:  ^[[1m^[[92m   Compiling^[[0m serde v1.0.228
1271:  ^[[1m^[[92m   Compiling^[[0m serde_json v1.0.149
1272:  ^[[1m^[[92m   Compiling^[[0m serde_path_to_error v0.1.20
1273:  ^[[1m^[[92m   Compiling^[[0m uuid v1.19.0
...

1348:  test parser::tests::parses_lists ... ok
1349:  test parser::tests::parses_list_values ... ok
1350:  test parser::tests::parses_multiple_stats ... ok
1351:  test parser::tests::parses_rollup_stats_keyword ... ok
1352:  test parser::tests::parses_rollup_stats_with_filters ... ok
1353:  test parser::tests::parses_stats_expression ... ok
1354:  test parser::tests::parses_stats_with_field ... ok
1355:  test parser::tests::parses_time ... ok
1356:  test parser::tests::parses_unquoted_stats_alias_with_following_tokens ... ok
1357:  test parser::tests::parses_unquoted_stats_alias ... ok
1358:  test parser::tests::rejects_empty_rollup_stats ... ok
1359:  test parser::tests::rejects_overly_long_stats_expression ... ok
1360:  test parser::tests::rejects_stats_alias_missing_identifier ... ok
1361:  test query::agents::tests::builds_query_with_type_id_filter ... ok
1362:  test query::agents::tests::builds_query_with_gateway_filter ... ok
1363:  test query::agents::tests::unknown_filter_field_returns_error ... ok
1364:  test parser::tests::rejects_list_filters_over_limit ... ok
1365:  test query::disk_metrics::tests::unknown_filter_field_returns_error ... ok
1366:  test query::cpu_metrics::tests::unknown_filter_field_returns_error ... ok
1367:  test query::cpu_metrics::tests::stats_query_matches_cpu_language_reference ... ok
1368:  test query::gateways::tests::unknown_filter_field_returns_error ... ok
1369:  test query::logs::tests::rollup_stats_severity_builds_cagg_query ... ok
1370:  test query::logs::tests::rollup_stats_severity_with_service_filter ... ok
1371:  test query::interfaces::tests::stats_count_interfaces_emits_count_query ... ok
1372:  test query::logs::tests::rollup_stats_unknown_type_returns_error ... ok
1373:  test query::logs::tests::stats_query_counts_logs_for_service ... ok
1374:  test query::logs::tests::unknown_filter_field_returns_error ... ok
1375:  test query::logs::tests::unknown_stats_filter_field_returns_error ... ok
1376:  test query::otel_metrics::tests::unknown_filter_field_returns_error ... ok
1377:  test query::memory_metrics::tests::unknown_filter_field_returns_error ... ok
1378:  test query::services::tests::unknown_filter_field_returns_error ... ok
1379:  test query::process_metrics::tests::unknown_filter_field_returns_error ... ok
1380:  test query::tests::services_docs_example_service_type_timeframe ... ok
1381:  test query::tests::devices_docs_example_discovery_sources_contains_all ... ok
1382:  test query::tests::gateways_docs_example_health_and_status ... ok
1383:  test query::tests::devices_docs_example_available_false ... ok
1384:  test query::tests::translate_graph_cypher_rejects_mutations ... ok
1385:  test query::tests::interfaces_docs_example_ip_addresses_contains_any ... ok
1386:  test query::tests::devices_docs_example_available_true ... ok
1387:  test query::tests::translate_graph_cypher_wraps_rows_as_topology_payload ... ok
1388:  test query::tests::translate_downsample_emits_time_bucket_query ... ok
1389:  test query::timeseries_metrics::tests::unknown_filter_field_returns_error ... ok
1390:  ^[[1m^[[92m     Running^[[0m unittests src/main.rs (/home/runner/_work/serviceradar/serviceradar/target/debug/deps/srql-37c8acb55abeef22)
1391:  test query::traces::tests::unknown_filter_field_returns_error ... ok
1392:  test time::tests::parses_open_end_absolute_range ... ok
1393:  test time::tests::parses_absolute_range ... ok
1394:  test time::tests::parses_open_start_absolute_range ... ok
1395:  test time::tests::serializes_relative_days ... ok
1396:  test time::tests::serializes_relative_hours ... ok
1397:  test time::tests::serializes_today ... ok
1398:  test time::tests::rejects_absolute_range_exceeding_limit ... ok
1399:  test query::tests::translate_includes_visualization_metadata ... ok
1400:  test time::tests::rejects_open_end_range_exceeding_limit ... ok
1401:  test time::tests::serializes_absolute_range ... ok
1402:  test time::tests::parses_relative_days ... ok
1403:  test query::tests::translate_param_arity_matches_sql_placeholders ... ok
1404:  test result: ok. 60 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
1405:  ^[[1m^[[92m     Running^[[0m tests/api.rs (/home/runner/_work/serviceradar/serviceradar/target/debug/deps/api-a8d9dcc3b48816a3)
1406:  running 0 tests
1407:  test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
1408:  running 1 test
1409:  test srql_api_queries ... FAILED
1410:  failures:
1411:  ---- srql_api_queries stdout ----
1412:  [srql-test] SRQL_TEST_DATABASE_URL: user=srql db=srql_fixture hosts=["srql-fixture.serviceradar.cloud"] port=5432
1413:  [srql-test] SRQL_TEST_ADMIN_URL: user=srql_hydra db=postgres hosts=["srql-fixture.serviceradar.cloud"] port=5432
1414:  thread 'srql_api_queries' (9213) panicked at rust/srql/tests/support/harness.rs:74:10:
1415:  failed to acquire remote fixture lock: db error
1416:  Caused by:
1417:  FATAL: pg_hba.conf rejects connection for host "10.42.68.65", user "srql_hydra", database "postgres", no encryption
1418:  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
1419:  failures:
1420:  srql_api_queries
1421:  test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
1422:  ^[[1m^[[91merror^[[0m: test failed, to rerun pass `--test api`
1423:  ##[error]Process completed with exit code 101.
1424:  Post job cleanup.

Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2241#issuecomment-3733444870 Original created: 2026-01-10T20:01:15Z --- ## CI Feedback 🧐 A test triggered by this PR failed. Here is an AI-generated analysis of the failure: <table><tr><td> **Action:** test-rust (rust/srql)</td></tr> <tr><td> **Failed stage:** [Run Tests](https://github.com/carverauto/serviceradar/actions/runs/20883583691/job/60003453422) [❌] </td></tr> <tr><td> **Failed test name:** srql_api_queries </td></tr> <tr><td> **Failure summary:** The action failed because the integration test <code>srql_api_queries</code> crashed while trying to connect to <br>the remote PostgreSQL fixture.<br> - The panic occurred in <code>rust/srql/tests/support/harness.rs:74:10</code> when <br>the test tried to acquire the remote fixture lock and received a database error.<br> - The underlying <br>cause is a PostgreSQL access policy/TLS mismatch: <code>FATAL: pg_hba.conf rejects connection for host </code><br><code>"10.42.68.65", user "srql_hydra", database "postgres", no encryption</code>.<br> This indicates the DB <br>server’s <code>pg_hba.conf</code> does not allow connections from the GitHub runner IP without encryption (or <br>does not allow that host/user/db combination at all), so the test cannot proceed.<br> </td></tr> <tr><td> <details><summary>Relevant error logs:</summary> ```yaml 1: Runner name: 'arc-runner-set-hk6mk-runner-b62bm' 2: Runner group name: 'Default' ... 166: ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m 167: ^[[36;1m sudo apt-get update^[[0m 168: ^[[36;1m sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m 169: ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m 170: ^[[36;1m sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 171: ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m 172: ^[[36;1m sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 173: ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m 174: ^[[36;1m sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 175: ^[[36;1melse^[[0m 176: ^[[36;1m echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m 177: ^[[36;1m exit 1^[[0m 178: ^[[36;1mfi^[[0m 179: ^[[36;1m^[[0m 180: ^[[36;1mensure_pkg_config^[[0m 181: ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m 182: shell: /usr/bin/bash -e {0} ... 221: libprotoc 3.12.4 222: ##[group]Run bazelbuild/setup-bazelisk@v3 223: with: 224: bazelisk-version: 1.x 225: token: *** 226: env: 227: BUILDBUDDY_ORG_API_KEY: *** 228: SRQL_TEST_DATABASE_URL: *** 229: SRQL_TEST_ADMIN_URL: *** 230: ##[endgroup] 231: Attempting to download 1.x... 232: Acquiring v1.27.0 from https://github.com/bazelbuild/bazelisk/releases/download/v1.27.0/bazelisk-linux-amd64 233: Adding to the cache ... 234: Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.27.0/x64 235: Added bazelisk to the path 236: ##[warning]Failed to restore: Cache service responded with 400 237: Restored bazelisk cache dir @ /home/runner/.cache/bazelisk ... 293: targets: 294: components: clippy 295: ##[endgroup] 296: ##[group]Run : set $CARGO_HOME 297: ^[[36;1m: set $CARGO_HOME^[[0m 298: ^[[36;1mecho CARGO_HOME=${CARGO_HOME:-"$HOME/.cargo"} >> $GITHUB_ENV^[[0m 299: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 300: env: 301: BUILDBUDDY_ORG_API_KEY: *** 302: SRQL_TEST_DATABASE_URL: *** 303: SRQL_TEST_ADMIN_URL: *** 304: ##[endgroup] 305: ##[group]Run : install rustup if needed 306: ^[[36;1m: install rustup if needed^[[0m 307: ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m 308: ^[[36;1m curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m 309: ^[[36;1m echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m ... 409: ^[[36;1m echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse >> $GITHUB_ENV^[[0m 410: ^[[36;1m elif rustc +stable --version --verbose | grep -q '^release: 1\.6[67]\.'; then^[[0m 411: ^[[36;1m touch "/home/runner/_work/_temp"/.implicit_cargo_registries_crates_io_protocol || true^[[0m 412: ^[[36;1m echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git >> $GITHUB_ENV^[[0m 413: ^[[36;1m fi^[[0m 414: ^[[36;1mfi^[[0m 415: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 416: env: 417: BUILDBUDDY_ORG_API_KEY: *** 418: SRQL_TEST_DATABASE_URL: *** 419: SRQL_TEST_ADMIN_URL: *** 420: CARGO_HOME: /home/runner/.cargo 421: CARGO_INCREMENTAL: 0 422: CARGO_TERM_COLOR: always 423: ##[endgroup] 424: ##[group]Run : work around spurious network errors in curl 8.0 425: ^[[36;1m: work around spurious network errors in curl 8.0^[[0m 426: ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m ... 584: ^[[1m^[[92m Downloaded^[[0m tokio-stream v0.1.18 585: ^[[1m^[[92m Downloaded^[[0m tokio-rustls v0.26.4 586: ^[[1m^[[92m Downloaded^[[0m tinyvec v1.10.0 587: ^[[1m^[[92m Downloaded^[[0m rustls-webpki v0.101.7 588: ^[[1m^[[92m Downloaded^[[0m tracing-log v0.2.0 589: ^[[1m^[[92m Downloaded^[[0m tower-service v0.3.3 590: ^[[1m^[[92m Downloaded^[[0m tower-layer v0.3.3 591: ^[[1m^[[92m Downloaded^[[0m tonic-prost-build v0.14.2 592: ^[[1m^[[92m Downloaded^[[0m logos v0.15.1 593: ^[[1m^[[92m Downloaded^[[0m tonic-prost v0.14.2 594: ^[[1m^[[92m Downloaded^[[0m ring v0.17.14 595: ^[[1m^[[92m Downloaded^[[0m tonic-build v0.14.2 596: ^[[1m^[[92m Downloaded^[[0m toml_write v0.1.2 597: ^[[1m^[[92m Downloaded^[[0m toml_datetime v0.6.11 598: ^[[1m^[[92m Downloaded^[[0m tokio-postgres-rustls v0.13.0 599: ^[[1m^[[92m Downloaded^[[0m thiserror-impl v2.0.17 600: ^[[1m^[[92m Downloaded^[[0m thiserror-impl v1.0.69 601: ^[[1m^[[92m Downloaded^[[0m spiffe v0.6.7 602: ^[[1m^[[92m Downloaded^[[0m serde_json v1.0.149 603: ^[[1m^[[92m Downloaded^[[0m tokio-macros v2.6.0 604: ^[[1m^[[92m Downloaded^[[0m tls_codec_derive v0.4.2 605: ^[[1m^[[92m Downloaded^[[0m tls_codec v0.4.2 606: ^[[1m^[[92m Downloaded^[[0m tinyvec_macros v0.1.1 607: ^[[1m^[[92m Downloaded^[[0m tinystr v0.8.2 608: ^[[1m^[[92m Downloaded^[[0m time-macros v0.2.24 609: ^[[1m^[[92m Downloaded^[[0m time-core v0.1.6 610: ^[[1m^[[92m Downloaded^[[0m thread_local v1.1.9 611: ^[[1m^[[92m Downloaded^[[0m thiserror v2.0.17 612: ^[[1m^[[92m Downloaded^[[0m thiserror v1.0.69 613: ^[[1m^[[92m Downloaded^[[0m tempfile v3.24.0 ... 625: ^[[1m^[[92m Downloaded^[[0m spki v0.7.3 626: ^[[1m^[[92m Downloaded^[[0m slab v0.4.11 627: ^[[1m^[[92m Downloaded^[[0m siphasher v1.0.1 628: ^[[1m^[[92m Downloaded^[[0m simple_asn1 v0.6.3 629: ^[[1m^[[92m Downloaded^[[0m signal-hook-registry v1.4.8 630: ^[[1m^[[92m Downloaded^[[0m shlex v1.3.0 631: ^[[1m^[[92m Downloaded^[[0m sharded-slab v0.1.7 632: ^[[1m^[[92m Downloaded^[[0m serde_with_macros v3.16.1 633: ^[[1m^[[92m Downloaded^[[0m serde_derive v1.0.228 634: ^[[1m^[[92m Downloaded^[[0m serde v1.0.228 635: ^[[1m^[[92m Downloaded^[[0m schemars v1.2.0 636: ^[[1m^[[92m Downloaded^[[0m schemars v0.9.0 637: ^[[1m^[[92m Downloaded^[[0m rustls-webpki v0.103.8 638: ^[[1m^[[92m Downloaded^[[0m rand v0.9.2 639: ^[[1m^[[92m Downloaded^[[0m prost-reflect v0.16.3 640: ^[[1m^[[92m Downloaded^[[0m serde_path_to_error v0.1.20 641: ^[[1m^[[92m Downloaded^[[0m semver v1.0.27 ... 812: ^[[1m^[[92m Checking^[[0m scopeguard v1.2.0 813: ^[[1m^[[92m Compiling^[[0m serde_core v1.0.228 814: ^[[1m^[[92m Checking^[[0m futures-sink v0.3.31 815: ^[[1m^[[92m Checking^[[0m once_cell v1.21.3 816: ^[[1m^[[92m Compiling^[[0m autocfg v1.5.0 817: ^[[1m^[[92m Checking^[[0m memchr v2.7.6 818: ^[[1m^[[92m Compiling^[[0m fnv v1.0.7 819: ^[[1m^[[92m Checking^[[0m pin-utils v0.1.0 820: ^[[1m^[[92m Checking^[[0m futures-task v0.3.31 821: ^[[1m^[[92m Compiling^[[0m regex-syntax v0.8.8 822: ^[[1m^[[92m Compiling^[[0m serde v1.0.228 823: ^[[1m^[[92m Checking^[[0m equivalent v1.0.2 824: ^[[1m^[[92m Compiling^[[0m zerocopy v0.8.33 825: ^[[1m^[[92m Checking^[[0m hashbrown v0.16.1 826: ^[[1m^[[92m Checking^[[0m stable_deref_trait v1.2.1 827: ^[[1m^[[92m Compiling^[[0m thiserror v2.0.17 828: ^[[1m^[[92m Compiling^[[0m heck v0.5.0 ... 902: ^[[1m^[[92m Checking^[[0m matchit v0.8.4 903: ^[[1m^[[92m Compiling^[[0m oid-registry v0.8.1 904: ^[[1m^[[92m Checking^[[0m fallible-iterator v0.2.0 905: ^[[1m^[[92m Compiling^[[0m logos-codegen v0.15.1 906: ^[[1m^[[92m Compiling^[[0m pq-sys v0.7.5 907: ^[[1m^[[92m Checking^[[0m num-integer v0.1.46 908: ^[[1m^[[92m Checking^[[0m block-buffer v0.10.4 909: ^[[1m^[[92m Checking^[[0m crypto-common v0.1.7 910: ^[[1m^[[92m Checking^[[0m hashbrown v0.12.3 911: ^[[1m^[[92m Checking^[[0m utf8_iter v1.0.4 912: ^[[1m^[[92m Checking^[[0m ryu v1.0.22 913: ^[[1m^[[92m Compiling^[[0m diesel_derives v2.3.6 914: ^[[1m^[[92m Checking^[[0m phf_shared v0.13.1 915: ^[[1m^[[92m Checking^[[0m pem v3.0.6 916: ^[[1m^[[92m Checking^[[0m num-bigint v0.4.6 917: ^[[1m^[[92m Compiling^[[0m thiserror v1.0.69 918: ^[[1m^[[92m Checking^[[0m unicode-normalization v0.1.25 ... 945: ^[[1m^[[92m Compiling^[[0m synstructure v0.13.2 946: ^[[1m^[[92m Compiling^[[0m darling_core v0.21.3 947: ^[[1m^[[92m Compiling^[[0m diesel_table_macro_syntax v0.3.0 948: ^[[1m^[[92m Compiling^[[0m pulldown-cmark-to-cmark v21.1.0 949: ^[[1m^[[92m Checking^[[0m time v0.3.44 950: ^[[1m^[[92m Checking^[[0m http-body v1.0.1 951: ^[[1m^[[92m Checking^[[0m http-body-util v0.1.3 952: ^[[1m^[[92m Compiling^[[0m tonic-build v0.14.2 953: ^[[1m^[[92m Checking^[[0m axum-core v0.5.6 954: ^[[1m^[[92m Compiling^[[0m tokio-macros v2.6.0 955: ^[[1m^[[92m Compiling^[[0m tracing-attributes v0.1.31 956: ^[[1m^[[92m Compiling^[[0m zerofrom-derive v0.1.6 957: ^[[1m^[[92m Compiling^[[0m displaydoc v0.2.5 958: ^[[1m^[[92m Compiling^[[0m yoke-derive v0.8.1 959: ^[[1m^[[92m Compiling^[[0m serde_derive v1.0.228 960: ^[[1m^[[92m Compiling^[[0m thiserror-impl v2.0.17 961: ^[[1m^[[92m Compiling^[[0m prost-derive v0.14.1 962: ^[[1m^[[92m Compiling^[[0m zerovec-derive v0.11.2 963: ^[[1m^[[92m Compiling^[[0m async-trait v0.1.89 964: ^[[1m^[[92m Compiling^[[0m zeroize_derive v1.4.3 965: ^[[1m^[[92m Compiling^[[0m prost-derive v0.13.5 966: ^[[1m^[[92m Compiling^[[0m miette-derive v7.6.0 967: ^[[1m^[[92m Compiling^[[0m der_derive v0.7.3 968: ^[[1m^[[92m Compiling^[[0m pin-project-internal v1.1.10 969: ^[[1m^[[92m Compiling^[[0m asn1-rs-impl v0.2.0 970: ^[[1m^[[92m Compiling^[[0m asn1-rs-derive v0.6.0 971: ^[[1m^[[92m Compiling^[[0m axum-macros v0.4.2 972: ^[[1m^[[92m Compiling^[[0m async-stream-impl v0.3.6 973: ^[[1m^[[92m Compiling^[[0m thiserror-impl v1.0.69 974: ^[[1m^[[92m Checking^[[0m errno v0.3.14 ... 976: ^[[1m^[[92m Checking^[[0m socket2 v0.6.1 977: ^[[1m^[[92m Checking^[[0m parking_lot v0.12.5 978: ^[[1m^[[92m Checking^[[0m mio v1.1.1 979: ^[[1m^[[92m Checking^[[0m getrandom v0.2.16 980: ^[[1m^[[92m Checking^[[0m zeroize v1.8.2 981: ^[[1m^[[92m Checking^[[0m socket2 v0.5.10 982: ^[[1m^[[92m Checking^[[0m rand_core v0.6.4 983: ^[[1m^[[92m Checking^[[0m rustls-pki-types v1.13.2 984: ^[[1m^[[92m Checking^[[0m rand_core v0.9.3 985: ^[[1m^[[92m Checking^[[0m tokio v1.49.0 986: ^[[1m^[[92m Checking^[[0m rand_chacha v0.3.1 987: ^[[1m^[[92m Checking^[[0m async-stream v0.3.6 988: ^[[1m^[[92m Checking^[[0m rand_chacha v0.9.0 989: ^[[1m^[[92m Checking^[[0m rustls-pemfile v2.2.0 990: ^[[1m^[[92m Compiling^[[0m logos-derive v0.15.1 991: ^[[1m^[[92m Checking^[[0m serde_path_to_error v0.1.20 992: ^[[1m^[[92m Checking^[[0m rand v0.9.2 ... 1116: ^[[1m^[[92m Compiling^[[0m futures-task v0.3.31 1117: ^[[1m^[[92m Compiling^[[0m stable_deref_trait v1.2.1 1118: ^[[1m^[[92m Compiling^[[0m libc v0.2.180 1119: ^[[1m^[[92m Compiling^[[0m anyhow v1.0.100 1120: ^[[1m^[[92m Compiling^[[0m serde_core v1.0.228 1121: ^[[1m^[[92m Compiling^[[0m tower-service v0.3.3 1122: ^[[1m^[[92m Compiling^[[0m zerocopy v0.8.33 1123: ^[[1m^[[92m Compiling^[[0m percent-encoding v2.3.2 1124: ^[[1m^[[92m Compiling^[[0m lock_api v0.4.14 1125: ^[[1m^[[92m Compiling^[[0m num-traits v0.2.19 1126: ^[[1m^[[92m Compiling^[[0m lazy_static v1.5.0 1127: ^[[1m^[[92m Compiling^[[0m subtle v2.6.1 1128: ^[[1m^[[92m Compiling^[[0m typenum v1.19.0 1129: ^[[1m^[[92m Compiling^[[0m futures-channel v0.3.31 1130: ^[[1m^[[92m Compiling^[[0m base64 v0.22.1 1131: ^[[1m^[[92m Compiling^[[0m thiserror v2.0.17 1132: ^[[1m^[[92m Compiling^[[0m untrusted v0.9.0 ... 1196: ^[[1m^[[92m Compiling^[[0m generic-array v0.14.7 1197: ^[[1m^[[92m Compiling^[[0m phf v0.13.1 1198: ^[[1m^[[92m Compiling^[[0m bitflags v2.10.0 1199: ^[[1m^[[92m Compiling^[[0m iana-time-zone v0.1.64 1200: ^[[1m^[[92m Compiling^[[0m regex-syntax v0.8.8 1201: ^[[1m^[[92m Compiling^[[0m winnow v0.7.14 1202: ^[[1m^[[92m Compiling^[[0m tinystr v0.8.2 1203: ^[[1m^[[92m Compiling^[[0m potential_utf v0.1.4 1204: ^[[1m^[[92m Compiling^[[0m async-stream v0.3.6 1205: ^[[1m^[[92m Compiling^[[0m rustls-pemfile v2.2.0 1206: ^[[1m^[[92m Compiling^[[0m icu_collections v2.1.1 1207: ^[[1m^[[92m Compiling^[[0m num-bigint v0.4.6 1208: ^[[1m^[[92m Compiling^[[0m downcast-rs v2.0.2 1209: ^[[1m^[[92m Compiling^[[0m stringprep v0.1.5 1210: ^[[1m^[[92m Compiling^[[0m icu_locale_core v2.1.1 1211: ^[[1m^[[92m Compiling^[[0m thiserror v1.0.69 1212: ^[[1m^[[92m Compiling^[[0m tracing-log v0.2.0 ... 1257: ^[[1m^[[92m Compiling^[[0m rand_chacha v0.9.0 1258: ^[[1m^[[92m Compiling^[[0m icu_properties v2.1.2 1259: ^[[1m^[[92m Compiling^[[0m logos v0.15.1 1260: ^[[1m^[[92m Compiling^[[0m icu_normalizer v2.1.1 1261: ^[[1m^[[92m Compiling^[[0m rand_chacha v0.3.1 1262: ^[[1m^[[92m Compiling^[[0m spki v0.7.3 1263: ^[[1m^[[92m Compiling^[[0m rusticata-macros v4.1.0 1264: ^[[1m^[[92m Compiling^[[0m rand v0.9.2 1265: ^[[1m^[[92m Compiling^[[0m pkcs8 v0.10.2 1266: ^[[1m^[[92m Compiling^[[0m x509-cert v0.2.5 1267: ^[[1m^[[92m Compiling^[[0m prost-types v0.13.5 1268: ^[[1m^[[92m Compiling^[[0m rand v0.8.5 1269: ^[[1m^[[92m Compiling^[[0m prost-types v0.14.1 1270: ^[[1m^[[92m Compiling^[[0m serde v1.0.228 1271: ^[[1m^[[92m Compiling^[[0m serde_json v1.0.149 1272: ^[[1m^[[92m Compiling^[[0m serde_path_to_error v0.1.20 1273: ^[[1m^[[92m Compiling^[[0m uuid v1.19.0 ... 1348: test parser::tests::parses_lists ... ok 1349: test parser::tests::parses_list_values ... ok 1350: test parser::tests::parses_multiple_stats ... ok 1351: test parser::tests::parses_rollup_stats_keyword ... ok 1352: test parser::tests::parses_rollup_stats_with_filters ... ok 1353: test parser::tests::parses_stats_expression ... ok 1354: test parser::tests::parses_stats_with_field ... ok 1355: test parser::tests::parses_time ... ok 1356: test parser::tests::parses_unquoted_stats_alias_with_following_tokens ... ok 1357: test parser::tests::parses_unquoted_stats_alias ... ok 1358: test parser::tests::rejects_empty_rollup_stats ... ok 1359: test parser::tests::rejects_overly_long_stats_expression ... ok 1360: test parser::tests::rejects_stats_alias_missing_identifier ... ok 1361: test query::agents::tests::builds_query_with_type_id_filter ... ok 1362: test query::agents::tests::builds_query_with_gateway_filter ... ok 1363: test query::agents::tests::unknown_filter_field_returns_error ... ok 1364: test parser::tests::rejects_list_filters_over_limit ... ok 1365: test query::disk_metrics::tests::unknown_filter_field_returns_error ... ok 1366: test query::cpu_metrics::tests::unknown_filter_field_returns_error ... ok 1367: test query::cpu_metrics::tests::stats_query_matches_cpu_language_reference ... ok 1368: test query::gateways::tests::unknown_filter_field_returns_error ... ok 1369: test query::logs::tests::rollup_stats_severity_builds_cagg_query ... ok 1370: test query::logs::tests::rollup_stats_severity_with_service_filter ... ok 1371: test query::interfaces::tests::stats_count_interfaces_emits_count_query ... ok 1372: test query::logs::tests::rollup_stats_unknown_type_returns_error ... ok 1373: test query::logs::tests::stats_query_counts_logs_for_service ... ok 1374: test query::logs::tests::unknown_filter_field_returns_error ... ok 1375: test query::logs::tests::unknown_stats_filter_field_returns_error ... ok 1376: test query::otel_metrics::tests::unknown_filter_field_returns_error ... ok 1377: test query::memory_metrics::tests::unknown_filter_field_returns_error ... ok 1378: test query::services::tests::unknown_filter_field_returns_error ... ok 1379: test query::process_metrics::tests::unknown_filter_field_returns_error ... ok 1380: test query::tests::services_docs_example_service_type_timeframe ... ok 1381: test query::tests::devices_docs_example_discovery_sources_contains_all ... ok 1382: test query::tests::gateways_docs_example_health_and_status ... ok 1383: test query::tests::devices_docs_example_available_false ... ok 1384: test query::tests::translate_graph_cypher_rejects_mutations ... ok 1385: test query::tests::interfaces_docs_example_ip_addresses_contains_any ... ok 1386: test query::tests::devices_docs_example_available_true ... ok 1387: test query::tests::translate_graph_cypher_wraps_rows_as_topology_payload ... ok 1388: test query::tests::translate_downsample_emits_time_bucket_query ... ok 1389: test query::timeseries_metrics::tests::unknown_filter_field_returns_error ... ok 1390: ^[[1m^[[92m Running^[[0m unittests src/main.rs (/home/runner/_work/serviceradar/serviceradar/target/debug/deps/srql-37c8acb55abeef22) 1391: test query::traces::tests::unknown_filter_field_returns_error ... ok 1392: test time::tests::parses_open_end_absolute_range ... ok 1393: test time::tests::parses_absolute_range ... ok 1394: test time::tests::parses_open_start_absolute_range ... ok 1395: test time::tests::serializes_relative_days ... ok 1396: test time::tests::serializes_relative_hours ... ok 1397: test time::tests::serializes_today ... ok 1398: test time::tests::rejects_absolute_range_exceeding_limit ... ok 1399: test query::tests::translate_includes_visualization_metadata ... ok 1400: test time::tests::rejects_open_end_range_exceeding_limit ... ok 1401: test time::tests::serializes_absolute_range ... ok 1402: test time::tests::parses_relative_days ... ok 1403: test query::tests::translate_param_arity_matches_sql_placeholders ... ok 1404: test result: ok. 60 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 1405: ^[[1m^[[92m Running^[[0m tests/api.rs (/home/runner/_work/serviceradar/serviceradar/target/debug/deps/api-a8d9dcc3b48816a3) 1406: running 0 tests 1407: test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 1408: running 1 test 1409: test srql_api_queries ... FAILED 1410: failures: 1411: ---- srql_api_queries stdout ---- 1412: [srql-test] SRQL_TEST_DATABASE_URL: user=srql db=srql_fixture hosts=["srql-fixture.serviceradar.cloud"] port=5432 1413: [srql-test] SRQL_TEST_ADMIN_URL: user=srql_hydra db=postgres hosts=["srql-fixture.serviceradar.cloud"] port=5432 1414: thread 'srql_api_queries' (9213) panicked at rust/srql/tests/support/harness.rs:74:10: 1415: failed to acquire remote fixture lock: db error 1416: Caused by: 1417: FATAL: pg_hba.conf rejects connection for host "10.42.68.65", user "srql_hydra", database "postgres", no encryption 1418: note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 1419: failures: 1420: srql_api_queries 1421: test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s 1422: ^[[1m^[[91merror^[[0m: test failed, to rerun pass `--test api` 1423: ##[error]Process completed with exit code 101. 1424: Post job cleanup. ``` </details></td></tr></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!2644
No description provided.