Fix/agent gateway genserver #2637

Merged
mfreeman451 merged 5 commits from refs/pull/2637/head into testing 2026-01-08 22:49:43 +00:00
mfreeman451 commented 2026-01-08 20:53:51 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2230
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2230
Original created: 2026-01-08T20:53:51Z
Original updated: 2026-01-08T22:49:53Z
Original head: carverauto/serviceradar:fix/agent_gateway_genserver
Original base: testing
Original merged: 2026-01-08T22:49:43Z by @mfreeman451

User description

IMPORTANT: Please sign the Developer Certificate of Origin

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

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

Describe your changes

Code checklist before requesting a review

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

PR Type

Enhancement, Tests


Description

  • Major architectural shift from poller to gateway model: Refactored agent from gRPC server to push-mode architecture with gateway client integration, replacing PollerService with AgentGatewayService

  • Agent Gateway gRPC Server implementation: New Elixir gRPC server for receiving status pushes from Go agents with mTLS authentication, multi-tenant security isolation, and agent enrollment/configuration management

  • Multi-tenant infrastructure: Implemented comprehensive tenant management with NATS account provisioning, encrypted seed storage, and per-tenant Horde registries for process isolation

  • NATS credentials file support: Added credentials file configuration across multiple components (trapd, zen consumer, flowgger, OTEL) for secure NATS connections

  • Device and agent runtime management: New GenServer-based device actor for runtime state management and agent resource with state machine lifecycle (connecting, connected, degraded, disconnected, unavailable)

  • Certificate generation and SPIFFE integration: X.509 certificate generation for tenant CAs and edge components with SPIFFE/SPIRE integration for distributed cluster security

  • Comprehensive database schema: Initial tenant schema migration with 1416 lines defining all tenant database tables including gateways, agents, devices, service checks, and monitoring infrastructure

  • Service health monitoring: Service heartbeat GenServer for periodic health status tracking of core, web, and gateway services

  • Edge onboarding packages: Ash-based context module for managing edge component onboarding with token generation, delivery, and component certificate signing

  • Statistics aggregation: Device statistics aggregator GenServer for periodic snapshots with device counts, availability, and capability breakdowns

  • Terminology updates: Renamed poller_id to gateway_id across multiple checkers (sysmon, rperf-client, trapd) and updated CLI subcommands (update-pollerupdate-gateway)

  • DataService KV client: New gRPC client for datasvc KV operations with automatic reconnection and exponential backoff strategy

  • Removed legacy poller infrastructure: Deleted poller-specific files, configurations, and Docker/Kubernetes deployment templates


Diagram Walkthrough

flowchart LR
  Agent["Go Agent<br/>Push Mode"]
  GW["Agent Gateway<br/>gRPC Server"]
  Tenant["Tenant Registry<br/>Horde Process Isolation"]
  NATS["NATS Account<br/>Multi-Tenant"]
  Device["Device Actor<br/>Runtime State"]
  Core["Core Cluster<br/>RPC Integration"]
  
  Agent -- "mTLS Push Status" --> GW
  GW -- "Extract Tenant" --> Tenant
  GW -- "Forward Data" --> Core
  Tenant -- "Manage Processes" --> Device
  Tenant -- "Account Provisioning" --> NATS
  Device -- "Heartbeat/Events" --> Core

File Walkthrough

Relevant files
Enhancement
31 files
main.go
Agent refactored to push mode with gateway integration     

cmd/agent/main.go

  • Refactored agent from gRPC server model to push-mode architecture with
    gateway client integration
  • Replaced complex config bootstrap with simplified file-based loading
    and embedded defaults fallback
  • Added signal handling for graceful shutdown with timeout protection
  • Introduced loadConfig() and runPushMode() functions for cleaner
    separation of concerns
+174/-74
main.go
NATS account service initialization and configuration       

cmd/data-services/main.go

  • Added NATS account service initialization with operator key management
  • Implemented resolver path configuration with environment variable
    overrides
  • Added system account credentials file setup for JWT resolver
    operations
  • Registered NATSAccountService gRPC server if configured
+68/-0   
main.go
CLI subcommands updated 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 dispatcher with nats admin resource routing
+16/-2   
main.go
SNMP checker 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
Core app gRPC service renamed to AgentGatewayService         

cmd/core/app/app.go

  • Renamed gRPC service registration from PollerService to
    AgentGatewayService
+1/-1     
config.rs
Zen consumer NATS credentials file configuration                 

cmd/consumers/zen/src/config.rs

  • Added optional nats_creds_file configuration field
  • Added validation to ensure nats_creds_file is not empty when provided
  • Added nats_creds_path() method to resolve credentials file path with
    support for relative paths
+26/-0   
server.rs
Sysmon checker updated to use gateway_id terminology         

cmd/checkers/sysmon/src/server.rs

  • Renamed poller_id field to gateway_id in status and results logging
  • Updated response structures to use gateway_id instead of poller_id
+6/-6     
config.rs
OTEL NATS credentials file configuration support                 

cmd/otel/src/config.rs

  • Added optional creds_file field to NATSConfigTOML struct
  • Added parsing logic to handle empty credentials file values
  • Updated NATSConfig struct to include creds_file as Option
+13/-0   
main.rs
Trapd NATS credentials and gateway_id updates                       

cmd/trapd/src/main.rs

  • Added credentials file support for NATS connections in both secure and
    non-secure modes
  • Updated connection logic to use credentials_file() when
    nats_creds_path is available
  • Renamed poller_id to gateway_id in status and results responses
+23/-3   
nats_output.rs
Flowgger NATS output credentials file support                       

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

  • Added creds_file field to NATSConfig struct
  • Implemented credentials file parsing with empty value handling
  • Applied credentials file to NATS connection options
+14/-0   
config.rs
Trapd NATS credentials file configuration                               

cmd/trapd/src/config.rs

  • Added optional nats_creds_file configuration field
  • Added validation to ensure credentials file is not empty
  • Added nats_creds_path() method to resolve file paths relative to
    security cert directory
+21/-0   
nats_output.rs
OTEL NATS output credentials file support                               

cmd/otel/src/nats_output.rs

  • Added creds_file field to NATSConfig struct with default None
  • Implemented credentials file application to NATS connection options
  • Added debug logging for credentials file usage
+7/-0     
grpc_server.rs
Zen consumer gRPC server gateway_id terminology                   

cmd/consumers/zen/src/grpc_server.rs

  • Renamed poller_id to gateway_id in status and results response
    structures
+2/-2     
nats.rs
Zen consumer NATS credentials file integration                     

cmd/consumers/zen/src/nats.rs

  • Added credentials file support to NATS connection initialization
  • Applied nats_creds_path() to connection options when available
+4/-0     
server.rs
RPerf checker gateway_id field addition                                   

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

  • Added gateway_id field to status and results response structures
+2/-0     
setup.rs
OTEL setup debug logging for credentials                                 

cmd/otel/src/setup.rs

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

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

  • Implemented comprehensive Ash-based context module for edge onboarding
    package management
  • Provides CRUD operations with token generation, delivery, revocation,
    and soft-delete
  • Added component certificate generation signed by tenant CA
  • Includes authorization and state machine integration for package
    lifecycle
+617/-0 
service_heartbeat.ex
Service heartbeat GenServer for health monitoring               

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

  • Implemented GenServer for Elixir service self-reporting via periodic
    heartbeats
  • Provides health status tracking for core, web, and gateway services
  • Includes state change reporting for degraded and recovered states
  • Integrates with HealthTracker for unified infrastructure monitoring
+320/-0 
agent_gateway_server.ex
Agent Gateway gRPC Server with mTLS Multi-Tenant Security

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex

  • Implements gRPC server for receiving status pushes from Go agents with
    mTLS authentication
  • Handles agent enrollment (hello), configuration requests (get_config),
    and status updates (push_status, stream_status)
  • Extracts tenant identity from mTLS certificates and enforces
    multi-tenant security isolation
  • Validates service statuses, manages agent heartbeats, and forwards
    data to core cluster via RPC
+1013/-0
agent.ex
Agent Resource with State Machine and Capability Management

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

  • Defines Agent resource for managing Go agents using Ash framework with
    state machine
  • Implements agent lifecycle states (connecting, connected, degraded,
    disconnected, unavailable)
  • Provides capability definitions (ICMP, TCP, HTTP, gRPC, DNS, Process,
    SNMP) and OCSF type mappings
  • Includes JSON API routes for agent registration, connection
    management, and heartbeat operations
+665/-0 
tenant_registry.ex
Multi-Tenant Registry with Horde-Based Process Isolation 

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

  • Manages per-tenant Horde registries and DynamicSupervisors for
    multi-tenant process isolation
  • Provides registry lifecycle management (creation, lookup, termination)
    with slug-based alias mapping
  • Implements process registration, discovery, and child process
    management within tenant boundaries
  • Includes convenience functions for agent and gateway registration and
    heartbeat tracking
+634/-0 
generator.ex
X.509 Certificate Generation for Tenant CAs and Components

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

  • Generates X.509 certificates for tenant intermediate CAs and edge
    components using Erlang's :public_key module
  • Creates tenant CA certificates (10-year validity) and component
    certificates (1-year validity) with SPIFFE URIs
  • Computes SPKI SHA-256 hashes for certificate verification and extracts
    tenant information from certificate CNs
  • Supports RSA 2048-bit keys with proper extensions (BasicConstraints,
    KeyUsage, SubjectAltName)
+541/-0 
alias_events.ex
Device Alias Lifecycle Event Tracking and Detection           

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

  • Tracks device alias lifecycle events (service IDs, IPs, collectors)
    with change detection
  • Provides AliasRecord struct for parsing alias metadata from device
    records
  • Builds lifecycle events for alias changes and processes alias updates
    with state transitions
  • Supports sighting counting and confirmation thresholds for alias
    validation
+649/-0 
hash_password.ex
Bcrypt Password Hashing Change for Ash Resources                 

elixir/serviceradar_core/lib/serviceradar/identity/changes/hash_password.ex

  • Implements Ash change for bcrypt password hashing in create and update
    actions
  • Hashes password argument and stores result in hashed_password
    attribute
  • Handles nil and empty password cases gracefully
+24/-0   
account_client.ex
NATS Account Client for Multi-Tenant Isolation                     

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

  • New gRPC client module for NATS account management and tenant
    isolation
  • Implements account creation, user credential generation, JWT signing,
    and operator bootstrap
  • Provides channel management with fallback to fresh connections when
    DataService.Client unavailable
  • Includes helper functions for building protobuf request structures
    (limits, permissions, mappings)
+567/-0 
spiffe.ex
SPIFFE/SPIRE Integration for Cluster Security                       

elixir/serviceradar_core/lib/serviceradar/spiffe.ex

  • New SPIFFE/SPIRE integration module for distributed cluster security
  • Provides SSL/TLS options for ERTS distribution with SPIFFE certificate
    verification
  • Implements SPIFFE ID parsing, validation, and certificate expiry
    monitoring
  • Supports both filesystem and workload API modes for certificate
    loading
+564/-0 
stats_aggregator.ex
Device Statistics Aggregator with Periodic Snapshots         

elixir/serviceradar_core/lib/serviceradar/core/stats_aggregator.ex

  • New GenServer for periodic device statistics aggregation and caching
  • Computes snapshots with device counts, availability, activity, and
    capability breakdowns
  • Implements canonical record filtering and deduplication logic
  • Includes telemetry recording and alert handler integration
+628/-0 
tenant.ex
Tenant Resource with NATS Account Provisioning                     

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

  • New Ash resource representing tenants (organizations) in multi-tenant
    system
  • Defines tenant statuses (active, suspended, pending, deleted) and
    billing plans
  • Implements NATS account provisioning with encrypted seed storage via
    AshCloak
  • Includes custom actions for CA generation, registration, and NATS
    account management
+604/-0 
device.ex
Device Actor for Runtime State Management                               

elixir/serviceradar_core/lib/serviceradar/actors/device.ex

  • New GenServer representing device runtime state with in-memory caching
  • Manages device identity, health status, configuration, and event
    buffering
  • Implements lazy initialization, Horde registry integration, and
    hibernation
  • Provides health state machine, event aggregation, and periodic
    persistence
+613/-0 
client.ex
DataService KV Client with Reconnection Logic                       

elixir/serviceradar_core/lib/serviceradar/data_service/client.ex

  • New gRPC client for datasvc KV service with automatic reconnection
  • Implements put, get, delete, list_keys, and put_many operations
  • Includes exponential backoff reconnection strategy and connection
    pooling
  • Supports mTLS with configurable certificates and environment variable
    overrides
+511/-0 
reserved_tenant_slug.ex
Reserved Tenant Slug Validation                                                   

elixir/serviceradar_core/lib/serviceradar/identity/validations/reserved_tenant_slug.ex

  • New Ash validation ensuring reserved tenant slugs are only used for
    platform tenant
  • Validates slug constraints during tenant creation and updates
  • Extracts slug from changeset with fallback to existing data
+87/-0   
Documentation
2 files
main.go
API documentation updated for gateway terminology               

cmd/core/main.go

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

cmd/tools/config-sync/main.go

  • Updated role flag documentation to reference "gateway" instead of
    "poller"
+1/-1     
Tests
1 files
message_processor.rs
Zen message processor test configuration update                   

cmd/consumers/zen/src/message_processor.rs

  • Added nats_creds_file: None field to test configuration struct
+1/-0     
Configuration
1 files
20260107043446_initial_schema.exs
Initial tenant schema migration with full database structure

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

  • Created comprehensive initial schema migration with 1416 lines
    defining all tenant database tables
  • Includes tables for gateways, agents, devices, service checks, polling
    schedules, NATS infrastructure, and monitoring
  • Implements encryption for sensitive fields using AshCloak
  • Defines relationships, indexes, and constraints for multi-tenant
    architecture
+1416/-0
Configuration changes
1 files
.gitkeep
Credentials Directory Placeholder                                               

docker/compose/creds/.gitkeep

  • New empty placeholder file for credentials directory
+1/-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   
zen-consumer-with-otel.json +1/-0     
zen-consumer.json +1/-0     
config.json +4/-4     
config.json +4/-4     
BUILD.bazel +1/-0     
README.md +3/-3     
README.md +9/-12   
flowgger.toml +1/-0     
otel.toml +1/-0     
BUILD.bazel +0/-25   
config.json +0/-111 
main.go +0/-138 
BUILD.bazel +0/-25   
config.json +0/-77   
main.go +0/-123 
docker-compose.elx.yml +109/-0 
docker-compose.spiffe.yml +8/-157 
docker-compose.yml +318/-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   
datasvc.docker.json +3/-2     
datasvc.mtls.json +14/-1   
db-event-writer.docker.json +2/-2     
db-event-writer.mtls.json +3/-2     
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 +2/-1     
generate-certs.sh +214/-12
nats.docker.conf +16/-160
netflow-consumer.mtls.json +1/-0     
otel.docker.toml +2/-0     
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 +2/-1     
update-config.sh +1/-190 
zen.docker.json +2/-1     
BUILD.bazel +80/-84 
push_targets.bzl +2/-2     
AGENTS.md +8/-8     
CNCF_DAY0.md +31/-31 
CNCF_security_self_assessment.md +7/-7     
Additional files not shown

Imported from GitHub pull request. Original GitHub pull request: #2230 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2230 Original created: 2026-01-08T20:53:51Z Original updated: 2026-01-08T22:49:53Z Original head: carverauto/serviceradar:fix/agent_gateway_genserver Original base: testing Original merged: 2026-01-08T22:49:43Z by @mfreeman451 --- ### **User description** ## IMPORTANT: Please sign the Developer Certificate of Origin Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include a [DCO sign-off statement]( https://developercertificate.org/) indicating the DCO acceptance in one commit message. Here is an example DCO Signed-off-by line in a commit message: ``` Signed-off-by: J. Doe <j.doe@domain.com> ``` ## Describe your changes ## Issue ticket number and link ## Code checklist before requesting a review - [ ] I have signed the DCO? - [ ] The build completes without errors? - [ ] All tests are passing when running make test? ___ ### **PR Type** Enhancement, Tests ___ ### **Description** - **Major architectural shift from poller to gateway model**: Refactored agent from gRPC server to push-mode architecture with gateway client integration, replacing `PollerService` with `AgentGatewayService` - **Agent Gateway gRPC Server implementation**: New Elixir gRPC server for receiving status pushes from Go agents with mTLS authentication, multi-tenant security isolation, and agent enrollment/configuration management - **Multi-tenant infrastructure**: Implemented comprehensive tenant management with NATS account provisioning, encrypted seed storage, and per-tenant Horde registries for process isolation - **NATS credentials file support**: Added credentials file configuration across multiple components (trapd, zen consumer, flowgger, OTEL) for secure NATS connections - **Device and agent runtime management**: New GenServer-based device actor for runtime state management and agent resource with state machine lifecycle (connecting, connected, degraded, disconnected, unavailable) - **Certificate generation and SPIFFE integration**: X.509 certificate generation for tenant CAs and edge components with SPIFFE/SPIRE integration for distributed cluster security - **Comprehensive database schema**: Initial tenant schema migration with 1416 lines defining all tenant database tables including gateways, agents, devices, service checks, and monitoring infrastructure - **Service health monitoring**: Service heartbeat GenServer for periodic health status tracking of core, web, and gateway services - **Edge onboarding packages**: Ash-based context module for managing edge component onboarding with token generation, delivery, and component certificate signing - **Statistics aggregation**: Device statistics aggregator GenServer for periodic snapshots with device counts, availability, and capability breakdowns - **Terminology updates**: Renamed `poller_id` to `gateway_id` across multiple checkers (sysmon, rperf-client, trapd) and updated CLI subcommands (`update-poller` → `update-gateway`) - **DataService KV client**: New gRPC client for datasvc KV operations with automatic reconnection and exponential backoff strategy - **Removed legacy poller infrastructure**: Deleted poller-specific files, configurations, and Docker/Kubernetes deployment templates ___ ### Diagram Walkthrough ```mermaid flowchart LR Agent["Go Agent<br/>Push Mode"] GW["Agent Gateway<br/>gRPC Server"] Tenant["Tenant Registry<br/>Horde Process Isolation"] NATS["NATS Account<br/>Multi-Tenant"] Device["Device Actor<br/>Runtime State"] Core["Core Cluster<br/>RPC Integration"] Agent -- "mTLS Push Status" --> GW GW -- "Extract Tenant" --> Tenant GW -- "Forward Data" --> Core Tenant -- "Manage Processes" --> Device Tenant -- "Account Provisioning" --> NATS Device -- "Heartbeat/Events" --> Core ``` <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>31 files</summary><table> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>Agent refactored to push mode with gateway integration</code>&nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/agent/main.go <ul><li>Refactored agent from gRPC server model to push-mode architecture with <br>gateway client integration<br> <li> Replaced complex config bootstrap with simplified file-based loading <br>and embedded defaults fallback<br> <li> Added signal handling for graceful shutdown with timeout protection<br> <li> Introduced <code>loadConfig()</code> and <code>runPushMode()</code> functions for cleaner <br>separation of concerns</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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 key management<br> <li> Implemented resolver path configuration with environment variable <br>overrides<br> <li> Added system account credentials file setup for JWT resolver <br>operations<br> <li> Registered <code>NATSAccountService</code> gRPC server if configured</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816">+68/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>CLI subcommands updated for gateway and NATS operations</code>&nbsp; &nbsp; </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 dispatcher with <code>nats</code> admin resource routing</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-ed4d81d29a7267f93fd77e17993fd3491b9ef6ded18490b4514d10ed1d803bc2">+16/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>SNMP checker service renamed from poller to gateway</code>&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/2230/files#diff-f25402eade63525184cb5e7437accff93c7b9338eebe81add6dc5f2a9eb12550">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>app.go</strong><dd><code>Core app gRPC service renamed to AgentGatewayService</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/core/app/app.go <ul><li>Renamed gRPC service registration from <code>PollerService</code> to <br><code>AgentGatewayService</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-4ad8a289575edf3b163088617b7a40ae1305c29ced0c7d59b3751c57d6938072">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>Zen consumer NATS credentials file configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/config.rs <ul><li>Added optional <code>nats_creds_file</code> configuration field<br> <li> Added validation to ensure <code>nats_creds_file</code> is not empty when provided<br> <li> Added <code>nats_creds_path()</code> method to resolve credentials file path with <br>support for relative paths</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-05038f3867985e757de9027609950e682bad6d1992dac6acd7c28962a3c65dc4">+26/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server.rs</strong><dd><code>Sysmon checker updated to use gateway_id terminology</code>&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 status and results logging<br> <li> Updated response structures to use <code>gateway_id</code> instead of <code>poller_id</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33e">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>OTEL NATS credentials file configuration support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/otel/src/config.rs <ul><li>Added optional <code>creds_file</code> field to <code>NATSConfigTOML</code> struct<br> <li> Added parsing logic to handle empty credentials file values<br> <li> Updated <code>NATSConfig</code> struct to include <code>creds_file</code> as <code>Option<PathBuf></code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-abbaec651da3d6af96b482e0f77bb909b65dbe0cabd78b5803769cc9dab0a1b0">+13/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.rs</strong><dd><code>Trapd NATS credentials and gateway_id updates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/trapd/src/main.rs <ul><li>Added credentials file support for NATS connections in both secure and <br>non-secure modes<br> <li> Updated connection logic to use <code>credentials_file()</code> when <br><code>nats_creds_path</code> is available<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/2230/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 file support</code>&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> field to <code>NATSConfig</code> struct<br> <li> Implemented credentials file parsing with empty value handling<br> <li> Applied credentials file to NATS connection options</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-a82e2e4d413539bf0b414b5629665b19648447523994cba639c4d1238aa5a0c1">+14/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>config.rs</strong><dd><code>Trapd NATS credentials file configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/trapd/src/config.rs <ul><li>Added optional <code>nats_creds_file</code> configuration field<br> <li> Added validation to ensure credentials file is not empty<br> <li> Added <code>nats_creds_path()</code> method to resolve file paths relative to <br>security cert directory</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-c89b88ba4d2bf0a054d0ba69a672a92c30140b8d19503d67b980a218ffe3106d">+21/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>nats_output.rs</strong><dd><code>OTEL NATS output credentials file support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/otel/src/nats_output.rs <ul><li>Added <code>creds_file</code> field to <code>NATSConfig</code> struct with default <code>None</code><br> <li> Implemented credentials file application to NATS connection options<br> <li> Added debug logging for credentials file usage</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-6b585ea3564a481174e04da1270e2e13edd4e2b980d02a2652d6d21e6d82a498">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>grpc_server.rs</strong><dd><code>Zen consumer gRPC server gateway_id terminology</code>&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> to <code>gateway_id</code> in status and results response <br>structures</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-e4564a93f6cf84ff91cd3d8141fc9272ec9b4ec19defd107afa42be01fcfed5b">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>nats.rs</strong><dd><code>Zen consumer NATS credentials file integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/nats.rs <ul><li>Added credentials file support to NATS connection initialization<br> <li> Applied <code>nats_creds_path()</code> to connection options when available</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-97f7335def0ad5d644b594a1076ae2d7080b11259cbb8de22c7946cc8e4b39f8">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server.rs</strong><dd><code>RPerf checker gateway_id field addition</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/checkers/rperf-client/src/server.rs - Added `gateway_id` field to status and results response structures </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-bce0f4ca6548712f224b73816825d28e831acbbff7dbed3c98671ed50f65d028">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>setup.rs</strong><dd><code>OTEL setup debug logging for credentials</code>&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/2230/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 module implementation</code>&nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/edge/onboarding_packages.ex <ul><li>Implemented comprehensive Ash-based context module for edge onboarding <br>package management<br> <li> Provides CRUD operations with token generation, delivery, revocation, <br>and soft-delete<br> <li> Added component certificate generation signed by tenant CA<br> <li> Includes authorization and state machine integration for package <br>lifecycle</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-e4fe8e19bc324416302bb4c962f57133b3f62eb82053766844d881c522a473e5">+617/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>service_heartbeat.ex</strong><dd><code>Service heartbeat GenServer for health monitoring</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/infrastructure/service_heartbeat.ex <ul><li>Implemented GenServer for Elixir service self-reporting via periodic <br>heartbeats<br> <li> Provides health status tracking for core, web, and gateway services<br> <li> Includes state change reporting for degraded and recovered states<br> <li> Integrates with HealthTracker for unified infrastructure monitoring</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-2f74eb0839cbc5e48dc55edd899a75d724bab3295cbf4de69780926adae9a90c">+320/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>agent_gateway_server.ex</strong><dd><code>Agent Gateway gRPC Server with mTLS Multi-Tenant Security</code></dd></summary> <hr> elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex <ul><li>Implements gRPC server for receiving status pushes from Go agents with <br>mTLS authentication<br> <li> Handles agent enrollment (<code>hello</code>), configuration requests (<code>get_config</code>), <br>and status updates (<code>push_status</code>, <code>stream_status</code>)<br> <li> Extracts tenant identity from mTLS certificates and enforces <br>multi-tenant security isolation<br> <li> Validates service statuses, manages agent heartbeats, and forwards <br>data to core cluster via RPC</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-369a368073dc8ec1140bcea699005a1ce97a90cd59629df0bd18c71c7ffaae9f">+1013/-0</a></td> </tr> <tr> <td> <details> <summary><strong>agent.ex</strong><dd><code>Agent Resource with State Machine and Capability Management</code></dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/infrastructure/agent.ex <ul><li>Defines <code>Agent</code> resource for managing Go agents using Ash framework with <br>state machine<br> <li> Implements agent lifecycle states (connecting, connected, degraded, <br>disconnected, unavailable)<br> <li> Provides capability definitions (ICMP, TCP, HTTP, gRPC, DNS, Process, <br>SNMP) and OCSF type mappings<br> <li> Includes JSON API routes for agent registration, connection <br>management, and heartbeat operations</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-c56f92b6ce744cab3f2dc00dde92e2017cffdd12ad4618f7fa720252f2a6843a">+665/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>tenant_registry.ex</strong><dd><code>Multi-Tenant Registry with Horde-Based Process Isolation</code>&nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/cluster/tenant_registry.ex <ul><li>Manages per-tenant Horde registries and DynamicSupervisors for <br>multi-tenant process isolation<br> <li> Provides registry lifecycle management (creation, lookup, termination) <br>with slug-based alias mapping<br> <li> Implements process registration, discovery, and child process <br>management within tenant boundaries<br> <li> Includes convenience functions for agent and gateway registration and <br>heartbeat tracking</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-91248b3b128a2e3d9bea6ffdb5e0f295e4a1745e82f87687c640ad01416fb85d">+634/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>generator.ex</strong><dd><code>X.509 Certificate Generation for Tenant CAs and Components</code></dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex <ul><li>Generates X.509 certificates for tenant intermediate CAs and edge <br>components using Erlang's <code>:public_key</code> module<br> <li> Creates tenant CA certificates (10-year validity) and component <br>certificates (1-year validity) with SPIFFE URIs<br> <li> Computes SPKI SHA-256 hashes for certificate verification and extracts <br>tenant information from certificate CNs<br> <li> Supports RSA 2048-bit keys with proper extensions (BasicConstraints, <br>KeyUsage, SubjectAltName)</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-b48e4a9e1189da61e2a60e16f56fce81298d76b7cdab745107140fed3f6e48b4">+541/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>alias_events.ex</strong><dd><code>Device Alias Lifecycle Event Tracking and Detection</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex <ul><li>Tracks device alias lifecycle events (service IDs, IPs, collectors) <br>with change detection<br> <li> Provides <code>AliasRecord</code> struct for parsing alias metadata from device <br>records<br> <li> Builds lifecycle events for alias changes and processes alias updates <br>with state transitions<br> <li> Supports sighting counting and confirmation thresholds for alias <br>validation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-bc3743067ea774f59bc5665770f7110a2d6e90f6e1156a7717a1c287f8979d28">+649/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>hash_password.ex</strong><dd><code>Bcrypt Password Hashing Change for Ash Resources</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/identity/changes/hash_password.ex <ul><li>Implements Ash change for bcrypt password hashing in create and update <br>actions<br> <li> Hashes password argument and stores result in <code>hashed_password</code> <br>attribute<br> <li> Handles nil and empty password cases gracefully</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-7a83d2af7ffcff928ba3f58d175bfe63eaaae87a1994ba899e42080427739534">+24/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>account_client.ex</strong><dd><code>NATS Account Client for Multi-Tenant Isolation</code>&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>New gRPC client module for NATS account management and tenant <br>isolation<br> <li> Implements account creation, user credential generation, JWT signing, <br>and operator bootstrap<br> <li> Provides channel management with fallback to fresh connections when <br>DataService.Client unavailable<br> <li> Includes helper functions for building protobuf request structures <br>(limits, permissions, mappings)</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-2e18ac777ac600b12982ba9e9d5327e23ebd84c139a2add7976f8bf61283e554">+567/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>spiffe.ex</strong><dd><code>SPIFFE/SPIRE Integration for Cluster Security</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/spiffe.ex <ul><li>New SPIFFE/SPIRE integration module for distributed cluster security<br> <li> Provides SSL/TLS options for ERTS distribution with SPIFFE certificate <br>verification<br> <li> Implements SPIFFE ID parsing, validation, and certificate expiry <br>monitoring<br> <li> Supports both filesystem and workload API modes for certificate <br>loading</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-0cb8d921c19f671b66f91c0978e351e71d927c5f4694924984c9f1ed34d7ee78">+564/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>stats_aggregator.ex</strong><dd><code>Device Statistics Aggregator with Periodic Snapshots</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/core/stats_aggregator.ex <ul><li>New GenServer for periodic device statistics aggregation and caching<br> <li> Computes snapshots with device counts, availability, activity, and <br>capability breakdowns<br> <li> Implements canonical record filtering and deduplication logic<br> <li> Includes telemetry recording and alert handler integration</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-1f4ac8290be7d27cac0ed660e51a9b3b23a219a6bb43b3735f3c5a9768321031">+628/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>tenant.ex</strong><dd><code>Tenant Resource with NATS Account Provisioning</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/identity/tenant.ex <ul><li>New Ash resource representing tenants (organizations) in multi-tenant <br>system<br> <li> Defines tenant statuses (active, suspended, pending, deleted) and <br>billing plans<br> <li> Implements NATS account provisioning with encrypted seed storage via <br>AshCloak<br> <li> Includes custom actions for CA generation, registration, and NATS <br>account management</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-9d0658a5118ece5eac7a6326788fdf59407a52f87c4b9c9ac69e6900bc04dc2a">+604/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>device.ex</strong><dd><code>Device Actor for Runtime State Management</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/actors/device.ex <ul><li>New GenServer representing device runtime state with in-memory caching<br> <li> Manages device identity, health status, configuration, and event <br>buffering<br> <li> Implements lazy initialization, Horde registry integration, and <br>hibernation<br> <li> Provides health state machine, event aggregation, and periodic <br>persistence</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-eba1d95a852e4a736813c7b486da651704f20718e24f931c966ff3f37c421eea">+613/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>client.ex</strong><dd><code>DataService KV Client with Reconnection Logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/data_service/client.ex <ul><li>New gRPC client for datasvc KV service with automatic reconnection<br> <li> Implements put, get, delete, list_keys, and put_many operations<br> <li> Includes exponential backoff reconnection strategy and connection <br>pooling<br> <li> Supports mTLS with configurable certificates and environment variable <br>overrides</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-503e195ad79e05e12d7ad03a675f6e35ffdfc201b8571b0d30a220fe036e03a1">+511/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>reserved_tenant_slug.ex</strong><dd><code>Reserved Tenant Slug Validation</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; </dd></summary> <hr> elixir/serviceradar_core/lib/serviceradar/identity/validations/reserved_tenant_slug.ex <ul><li>New Ash validation ensuring reserved tenant slugs are only used for <br>platform tenant<br> <li> Validates slug constraints during tenant creation and updates<br> <li> Extracts slug from changeset with fallback to existing data</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-3495f8e60db2c1472c538ed33b6d2be79730d7938ad034d273281040ee1558cb">+87/-0</a>&nbsp; &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 updated for gateway terminology</code>&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/2230/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 updated</code>&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 <ul><li>Updated role flag documentation to reference "gateway" instead of <br>"poller"</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-bc6eeb1b05bcb9179525e32fac1de9926b5823ec3504be546ab10c5c9740f544">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>message_processor.rs</strong><dd><code>Zen message processor test configuration update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/consumers/zen/src/message_processor.rs - Added `nats_creds_file: None` field to test configuration struct </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-9fcbc5358a9009e60a8cd22d21e5a9ea652787c727732d0b869e0865495114c3">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration</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 full database structure</code></dd></summary> <hr> elixir/serviceradar_core/priv/repo/tenant_migrations/20260107043446_initial_schema.exs <ul><li>Created comprehensive initial schema migration with 1416 lines <br>defining all tenant database tables<br> <li> Includes tables for gateways, agents, devices, service checks, polling <br>schedules, NATS infrastructure, and monitoring<br> <li> Implements encryption for sensitive fields using AshCloak<br> <li> Defines relationships, indexes, and constraints for multi-tenant <br>architecture</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-0d217dc9822fab0d3390e8ec21040f98e67106e5c9126e043a9b701efcbfb576">+1416/-0</a></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>.gitkeep</strong><dd><code>Credentials Directory Placeholder</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> docker/compose/creds/.gitkeep - New empty placeholder file for credentials directory </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-d72c41aab2d6f2c230a4340dfefe7917cdd12bed942c825aa0d4c9875a637bac">+1/-0</a>&nbsp; &nbsp; &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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-a54ff182c7e8acf56acfd6e4b9c3ff41e2c41a31c9b211b2deb9df75d9a478f9">+177/-11</a></td> </tr> <tr> <td><strong>INSTALL.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-86ec281f99363b6b6eb1f49e21d83b7eeca93a35b552b9f305fffc6855e38ccd">+124/-49</a></td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-9faf6025eb0d3d38383f5b7ad2b733abeb38454d5e4de3e83994e94b12d87a50">+2/-26</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer-with-otel.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-68375f1f7847e1fbdf75664f6be65b1ad94ae6ce86ed73fc5964d65054668acb">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-4d308af9802a93a0f656e8c02a3b5fcd8991407bb18360f087470db74e1f9524">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-af9f49f931e282dca53d1f0521b036d222fe671f77e61a876a84cf4c6d7cca4d">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-c64b9ace832b8ea57a2be62f84166e03bb1904882635d444ec76a880cdf14cc0">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-78dc6bc53f1c760c66f43ff5f486bfe78a65bee8b2e0d4862293ec0892da2b29">+0/-123</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.elx.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-9562070d7ad4a3e9b2d06567008cf35de1d96448d914b3b45bf6c36d97cdd914">+109/-0</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.spiffe.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-603fd9e7d40841d174f26b95d0cb0c9537430bf3f7a5da3ccbba4ea3d8ac66c9">+8/-157</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-e45e45baeda1c1e73482975a664062aa56f20c03dd9d64a827aba57775bed0d3">+318/-269</a></td> </tr> <tr> <td><strong>Dockerfile.agent-gateway</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-ab4746a08fb1e0b307a1e47660cd22182e283a087cba87dcbff0fdfe750f44f1">+0/-80</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>datasvc.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/files#diff-9fc51271f7ef5bb460160013e24e44e829b730656891d26fc49d5fe72fbb3147">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>db-event-writer.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-7a33f95f7545499abf0ed9fc91b58499ab209639e4885019579c959583fc7496">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>FRICTION_POINTS.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-824f8797b418d4b9f5ea41e4a3741a0ed64b881f343072464489a76b7ea01008">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>generate-certs.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/files#diff-d4af38790e3657b7589cd37a7539d5308b032f11caba7aa740ddc86bf99f4415">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>pg_hba.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/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/2230/files#diff-1ab1a0e03e63bc02e0ef31992a7187a377927272ed2060150b40d44cc0ea3357">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>update-config.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-9ae50be83a13010a038389c74407ba1bde8cabcea0944e238c4b3374133f78bf">+1/-190</a>&nbsp; </td> </tr> <tr> <td><strong>zen.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-e060a3164cdc2746e0d9ad000fcf43c4bcdb05f4a41c586d7220e2ff2a7df01d">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-0e4db31c224a8f72ae8e870a849e38a59d74a2c7f7b04347b0b3eb07e20c5a80">+80/-84</a>&nbsp; </td> </tr> <tr> <td><strong>push_targets.bzl</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-4af33fe62caba04b6d479589c16cfb85babc39bae5c92595d4d4e31660738513">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>AGENTS.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-d00743dc4e878f258330deadcd4008e361276d760b58b672150c54bb1ac9758a">+8/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>CNCF_DAY0.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-49a3c958df12738f89e0f701e36d8aee08dc3e8bf275a0421159cff0c2e5a9ce">+31/-31</a>&nbsp; </td> </tr> <tr> <td><strong>CNCF_security_self_assessment.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-d64ffa4a0d2f25cc11d0731c714caa8b406a11b20bf63396fdb3144471e58105">+7/-7</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2230/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-08 20:55:17 +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/2230#issuecomment-3725755737
Original created: 2026-01-08T20:55:17Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Arbitrary file write

Description: The datasvc process accepts resolver/config output paths from environment variables (e.g.,
NATS_OPERATOR_CONFIG_PATH, NATS_RESOLVER_PATH) and then calls WriteOperatorConfig(), which
could enable an arbitrary file write/overwrite if an attacker can influence the process
environment or config (e.g., writing operator config into a sensitive filesystem
location).
main.go [83-130]

Referred Code
// 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 27 lines)
Sensitive log exposure

Description: Untrusted request fields are logged verbatim (e.g., req.details in GetStatus/GetResults),
which can leak sensitive data into logs and enable log-forging/injection; similar patterns
appear in other services in this PR (e.g., cmd/otel/src/setup.rs logs nats.creds_file
paths and multiple agent services log request details/IDs).
server.rs [214-220]

Referred Code
) -> Result<Response<monitoring::StatusResponse>, Status> {
    let req = request.into_inner();
    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
    );
    debug!("Processing GetStatus request");
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:
Potential panic unwrap: The new NATS creds-file parsing uses v.as_str().unwrap() which can panic on unexpected
config types instead of returning a handled error with context.

Referred Code
let creds_file = cfg.lookup("output.nats_creds_file").and_then(|v| {
    let value = v.as_str().unwrap().trim();
    if value.is_empty() {
        None
    } else {
        Some(PathBuf::from(value))
    }
});

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 info in logs: The PR logs potentially sensitive operational/security configuration details (e.g.,
allowed client identities lists, resolver paths, and system creds file locations) which
can aid attackers or leak environment secrets.

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 events: The PR introduces security-relevant operations (e.g., NATS account bootstrap/config
writes) but only emits ad-hoc logs and does not show explicit audit-trail events with
actor identity and outcome.

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: Secure Error Handling

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

Status:
Exception message exposure: The new crypto utility raises explicit decryption/configuration failure strings that may
become user-visible depending on caller boundaries and should be verified as
internal-only.

Referred Code
      :error -> raise "Decryption failed: invalid ciphertext or key"
    end
  else
    _ -> raise "Decryption failed: invalid encoded data"
  end
end

@doc """
Safely decrypts data, returning `{:ok, plaintext}` or `{:error, reason}`.
"""
@spec decrypt_safe(String.t()) :: {:ok, String.t()} | {:error, :decrypt_failed}
def decrypt_safe(encoded) do
  {:ok, decrypt(encoded)}
rescue
  _ -> {:error, :decrypt_failed}
end

@doc """
Hashes a download token for secure storage using SHA-256.

Returns a hex-encoded hash string.



 ... (clipped 26 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:
Unvalidated request logging: The PR logs req.details directly from incoming requests, which may contain
untrusted/sensitive content and should be validated/sanitized or redacted before logging.

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
);
debug!("Processing GetStatus request");

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/2230#issuecomment-3725755737 Original created: 2026-01-08T20:55:17Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/de420fceeb72fcba4bc5cc53a2d4504fe2bdb849 --> 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=2>⚪</td> <td><details><summary><strong>Arbitrary file write </strong></summary><br> <b>Description:</b> The datasvc process accepts resolver/config output paths from environment variables (e.g., <br><code>NATS_OPERATOR_CONFIG_PATH</code>, <code>NATS_RESOLVER_PATH</code>) and then calls <code>WriteOperatorConfig()</code>, which <br>could enable an arbitrary file write/overwrite if an attacker can influence the process <br>environment or config (e.g., writing operator config into a sensitive filesystem <br>location).<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2230/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R83-R130'>main.go [83-130]</a></strong><br> <details open><summary>Referred Code</summary> ```go // 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 27 lines) ``` </details></details></td></tr> <tr><td><details><summary><strong>Sensitive log exposure </strong></summary><br> <b>Description:</b> Untrusted request fields are logged verbatim (e.g., <code>req.details</code> in <code>GetStatus</code>/<code>GetResults</code>), <br>which can leak sensitive data into logs and enable log-forging/injection; similar patterns <br>appear in other services in this PR (e.g., <code>cmd/otel/src/setup.rs</code> logs <code>nats.creds_file</code> <br>paths and multiple agent services log request details/IDs).<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2230/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33eR214-R220'>server.rs [214-220]</a></strong><br> <details open><summary>Referred Code</summary> ```rust ) -> Result<Response<monitoring::StatusResponse>, Status> { let req = request.into_inner(); 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 ); debug!("Processing GetStatus request"); ``` </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=1>🟢</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 rowspan=2>🔴</td> <td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2230/files#diff-a82e2e4d413539bf0b414b5629665b19648447523994cba639c4d1238aa5a0c1R86-R93'><strong>Potential panic unwrap</strong></a>: The new NATS creds-file parsing uses <code>v.as_str().unwrap()</code> which can panic on unexpected <br>config types instead of returning a handled error with context.<br> <details open><summary>Referred Code</summary> ```rust let creds_file = cfg.lookup("output.nats_creds_file").and_then(|v| { let value = v.as_str().unwrap().trim(); if value.is_empty() { None } else { Some(PathBuf::from(value)) } }); ``` </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: 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/2230/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R91-R141'><strong>Sensitive info in logs</strong></a>: The PR logs potentially sensitive operational/security configuration details (e.g., <br>allowed client identities lists, resolver paths, and system creds file locations) which <br>can aid attackers or leak environment secrets.<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=3>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2230/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R82-R142'><strong>Missing audit events</strong></a>: The PR introduces security-relevant operations (e.g., NATS account bootstrap/config <br>writes) but only emits ad-hoc logs and does not show explicit audit-trail events with <br>actor identity and outcome.<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: 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:** <br><a href='https://github.com/carverauto/serviceradar/pull/2230/files#diff-3da7b9890490f8b2452af805bc006e60a4a973e2859de0c822dad0274e09fe9aR81-R127'><strong>Exception message exposure</strong></a>: The new crypto utility raises explicit decryption/configuration failure strings that may <br>become user-visible depending on caller boundaries and should be verified as <br>internal-only.<br> <details open><summary>Referred Code</summary> ```elixir :error -> raise "Decryption failed: invalid ciphertext or key" end else _ -> raise "Decryption failed: invalid encoded data" end end @doc """ Safely decrypts data, returning `{:ok, plaintext}` or `{:error, reason}`. """ @spec decrypt_safe(String.t()) :: {:ok, String.t()} | {:error, :decrypt_failed} def decrypt_safe(encoded) do {:ok, decrypt(encoded)} rescue _ -> {:error, :decrypt_failed} end @doc """ Hashes a download token for secure storage using SHA-256. Returns a hex-encoded hash string. ... (clipped 26 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/2230/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33eR216-R220'><strong>Unvalidated request logging</strong></a>: The PR logs <code>req.details</code> directly from incoming requests, which may contain <br>untrusted/sensitive content and should be validated/sanitized or redacted before logging.<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 ); debug!("Processing GetStatus request"); ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /compliance --update_compliance=true --> </td></tr></tbody></table> <details><summary>Compliance status legend</summary> 🟢 - Fully Compliant<br> 🟡 - Partial Compliant<br> 🔴 - Not Compliant<br> ⚪ - Requires Further Human Verification<br> 🏷️ - Compliance label<br> </details>
qodo-code-review[bot] commented 2026-01-08 20:56:40 +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/2230#issuecomment-3725765477
Original created: 2026-01-08T20:56:40Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Include tenant_id in CA return

Add the tenant_id to the map returned by generate_tenant_ca to ensure
generate_component_cert can function correctly.

elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [106-115]

 {:ok,
  %{
    certificate_pem: cert_pem,
    private_key_pem: key_pem,
    spki_sha256: spki_sha256,
    serial_number: serial_to_hex(serial),
    not_before: not_before,
    not_after: not_after,
-   subject_cn: subject_cn
+   subject_cn: subject_cn,
+   tenant_id: tenant.id
  }}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical data omission. The tenant_ca map returned by generate_tenant_ca is missing the tenant_id, which is essential for the subsequent generate_component_cert function to operate correctly. Adding it fixes a definite bug.

High
Fix push interval time unit

Correct the push interval calculation by multiplying the cfg.PushInterval value
by time.Second to ensure it is interpreted as seconds instead of nanoseconds.

cmd/agent/main.go [158-173]

-interval := time.Duration(cfg.PushInterval)
+interval := time.Duration(cfg.PushInterval) * time.Second
 if interval <= 0 {
     // Let NewPushLoop apply its default interval
     interval = 0
 } else {
     // Safety bounds against misconfiguration
     const (
         minPushInterval = 1 * time.Second
         maxPushInterval = 1 * time.Hour
     )
     if interval < minPushInterval {
         interval = minPushInterval
     } else if interval > maxPushInterval {
         interval = maxPushInterval
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential bug where a configuration value in seconds is misinterpreted as nanoseconds, leading to an incorrect push interval. Applying this fix is critical for the agent's correct timing behavior.

Medium
Prevent crashes from unavailable nodes

Add try/rescue blocks within the core_nodes/0 function to handle potential
:rpc.call errors, preventing crashes when a node is unavailable.

elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex [867-888]

 defp core_nodes do
   nodes = [Node.self() | Node.list()] |> Enum.uniq()
 
   coordinators =
     Enum.filter(nodes, fn node ->
-      case :rpc.call(node, Process, :whereis, [ServiceRadar.ClusterHealth], 5_000) do
-        pid when is_pid(pid) -> true
+      try do
+        case :rpc.call(node, Process, :whereis, [ServiceRadar.ClusterHealth], 5_000) do
+          pid when is_pid(pid) -> true
+          _ -> false
+        end
+      rescue
         _ -> false
       end
     end)
 
   if coordinators != [] do
     coordinators
   else
     Enum.filter(nodes, fn node ->
-      case :rpc.call(node, Process, :whereis, [ServiceRadar.Repo], 5_000) do
-        pid when is_pid(pid) -> true
+      try do
+        case :rpc.call(node, Process, :whereis, [ServiceRadar.Repo], 5_000) do
+          pid when is_pid(pid) -> true
+          _ -> false
+        end
+      rescue
         _ -> false
       end
     end)
   end
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential runtime crash due to an unhandled exception from :rpc.call in a distributed environment and provides a robust fix, significantly improving the service's fault tolerance.

Medium
Handle nil timestamps during deduplication
Suggestion Impact:Replaced Enum.max_by/3's DateTime comparator with a custom comparison function that safely handles nil timestamp values.

code diff:

@@ -514,8 +514,13 @@
     updates
     |> Enum.group_by(& &1.device_id)
     |> Enum.map(fn {_device_id, grouped} ->
-      # Keep the update with the latest timestamp
-      Enum.max_by(grouped, & &1.timestamp, DateTime)
+      # 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)
+      end)

Prevent a potential crash in deduplicate_by_device_id by providing a custom
comparator to Enum.max_by that correctly handles nil timestamp values.

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

 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
-    Enum.max_by(grouped, & &1.timestamp, DateTime)
+    # 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)
+    end)
   end)
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential FunctionClauseError when Enum.max_by/3 receives a nil timestamp, which is permitted by the device_update type. The proposed fix makes the function robust by providing a custom comparator that correctly handles nil values.

Medium
Prevent errors from missing tenant
Suggestion Impact:The commit added an explicit nil check for tenant_value and raises an ArgumentError with a clear message before calling TenantSchemas.schema_for_tenant/1, preventing downstream failures when the tenant cannot be resolved.

code diff:

+    if is_nil(tenant_value) do
+      raise ArgumentError,
+            "Tenant could not be resolved - missing tenant identifier in options or attributes"
+    end

In resolve_tenant/2, add a check to ensure a tenant identifier is found, and
raise an error if it is nil to prevent downstream failures.

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

 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 "Tenant could not be resolved. Missing tenant identifier in options or attributes."
+  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 identifies that a nil tenant identifier can cause a downstream error and proposes a valid guard clause. This improves robustness by failing early with a clear error message.

Medium
Use date-aware addition for validity

Replace the manual date calculation with DateTime.add/3 to correctly account for
leap years when setting the certificate's not_after date.

elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [315-320]

 defp validity_period(years) do
   now = DateTime.utc_now() |> DateTime.truncate(:second)
   not_before = DateTime.add(now, -60, :second)  # Allow 1 minute clock skew
-  not_after = DateTime.add(now, years * 365 * 24 * 60 * 60, :second)
+  not_after = DateTime.add(now, years, :year)
   {:ok, {not_before, not_after}}
 end

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the manual date calculation for certificate expiry does not account for leap years and proposes using DateTime.add/3 for a more accurate and robust implementation.

Low
General
Only write operator config when path set

Add a guard to ensure WriteOperatorConfig() is only called when
operatorConfigPath is not empty, preventing a potential error when only
resolverPath is set.

cmd/data-services/main.go [117-130]

 if operatorConfigPath != "" || resolverPath != "" {
     natsAccountServer.SetResolverPaths(operatorConfigPath, resolverPath)
     log.Printf("NATS resolver paths configured: operator=%s resolver=%s", operatorConfigPath, resolverPath)
-    // If operator is already initialized, write the config now
-    // This ensures config files exist even when datasvc restarts with an existing operator
-    if operator != nil {
+    // Only write operator config if we have both an operator and a valid config path
+    if operator != nil && operatorConfigPath != "" {
         if err := natsAccountServer.WriteOperatorConfig(); err != nil {
             log.Printf("Warning: failed to write initial operator config: %v", err)
         } else {
             log.Printf("Wrote initial operator config to %s", operatorConfigPath)
         }
     }
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies a potential panic by adding a necessary check for a non-empty operatorConfigPath before attempting to write a file, thus preventing a runtime error.

Medium
Refactor duplicated code for clarity

Refactor the duplicated server.Stop call to a single location to handle cleanup
for all exit paths from the push loop, improving code maintainability.

cmd/agent/main.go [231-245]

 	case err := <-errChan:
-		if err != nil && !errors.Is(err, context.Canceled) {
-			stopCtx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second)
-			defer stopCancel()
-			if stopErr := server.Stop(stopCtx); stopErr != nil {
-				log.Error().Err(stopErr).Msg("Error stopping agent services")
-			}
-			return fmt.Errorf("push loop error: %w", err)
-		}
-
+		// Always attempt to gracefully stop the server's services.
 		stopCtx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second)
 		defer stopCancel()
 		if stopErr := server.Stop(stopCtx); stopErr != nil {
 			log.Error().Err(stopErr).Msg("Error stopping agent services")
 		}
 
+		// If the error was not a cancellation, it's a real problem, so return it.
+		if err != nil && !errors.Is(err, context.Canceled) {
+			return fmt.Errorf("push loop error: %w", err)
+		}
+
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This suggestion correctly identifies and resolves duplicated code by refactoring the shutdown logic, which improves code maintainability and readability.

Low
Simplify trailing JSON data check

Replace the complex logic for detecting trailing JSON data with a simpler, more
idiomatic check using dec.More().

cmd/agent/main.go [133-137]

-	if err := dec.Decode(&struct{}{}); err == nil {
+	if dec.More() {
 		return nil, fmt.Errorf("failed to parse config: %w", errConfigTrailingData)
-	} else if !errors.Is(err, io.EOF) {
-		return nil, fmt.Errorf("failed to parse config: %w", err)
 	}
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion proposes using the idiomatic dec.More() function to check for trailing JSON data, which simplifies the code and improves readability.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2230#issuecomment-3725765477 Original created: 2026-01-08T20:56:40Z --- ## PR Code Suggestions ✨ <!-- de420fc --> 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=6>Possible issue</td> <td> <details><summary>Include tenant_id in CA return</summary> ___ **Add the <code>tenant_id</code> to the map returned by <code>generate_tenant_ca</code> to ensure <br><code>generate_component_cert</code> can function correctly.** [elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [106-115]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-b48e4a9e1189da61e2a60e16f56fce81298d76b7cdab745107140fed3f6e48b4R106-R115) ```diff {:ok, %{ certificate_pem: cert_pem, private_key_pem: key_pem, spki_sha256: spki_sha256, serial_number: serial_to_hex(serial), not_before: not_before, not_after: not_after, - subject_cn: subject_cn + subject_cn: subject_cn, + tenant_id: tenant.id }} ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical data omission. The `tenant_ca` map returned by `generate_tenant_ca` is missing the `tenant_id`, which is essential for the subsequent `generate_component_cert` function to operate correctly. Adding it fixes a definite bug. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Fix push interval time unit</summary> ___ **Correct the push interval calculation by multiplying the <code>cfg.PushInterval</code> value <br>by <code>time.Second</code> to ensure it is interpreted as seconds instead of nanoseconds.** [cmd/agent/main.go [158-173]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3R158-R173) ```diff -interval := time.Duration(cfg.PushInterval) +interval := time.Duration(cfg.PushInterval) * time.Second if interval <= 0 { // Let NewPushLoop apply its default interval interval = 0 } else { // Safety bounds against misconfiguration const ( minPushInterval = 1 * time.Second maxPushInterval = 1 * time.Hour ) if interval < minPushInterval { interval = minPushInterval } else if interval > maxPushInterval { interval = maxPushInterval } } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=1 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: This suggestion correctly identifies a potential bug where a configuration value in seconds is misinterpreted as nanoseconds, leading to an incorrect push interval. Applying this fix is critical for the agent's correct timing behavior. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Prevent crashes from unavailable nodes<!-- not_implemented --></summary> ___ **Add <code>try/rescue</code> blocks within the <code>core_nodes/0</code> function to handle potential <br><code>:rpc.call</code> errors, preventing crashes when a node is unavailable.** [elixir/serviceradar_agent_gateway/lib/serviceradar_agent_gateway/agent_gateway_server.ex [867-888]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-369a368073dc8ec1140bcea699005a1ce97a90cd59629df0bd18c71c7ffaae9fR867-R888) ```diff defp core_nodes do nodes = [Node.self() | Node.list()] |> Enum.uniq() coordinators = Enum.filter(nodes, fn node -> - case :rpc.call(node, Process, :whereis, [ServiceRadar.ClusterHealth], 5_000) do - pid when is_pid(pid) -> true + try do + case :rpc.call(node, Process, :whereis, [ServiceRadar.ClusterHealth], 5_000) do + pid when is_pid(pid) -> true + _ -> false + end + rescue _ -> false end end) if coordinators != [] do coordinators else Enum.filter(nodes, fn node -> - case :rpc.call(node, Process, :whereis, [ServiceRadar.Repo], 5_000) do - pid when is_pid(pid) -> true + try do + case :rpc.call(node, Process, :whereis, [ServiceRadar.Repo], 5_000) do + pid when is_pid(pid) -> true + _ -> false + end + rescue _ -> false 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 a potential runtime crash due to an unhandled exception from `:rpc.call` in a distributed environment and provides a robust fix, significantly improving the service's fault tolerance. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>✅ <s>Handle nil timestamps during deduplication</s></summary> ___ <details><summary><b>Suggestion Impact:</b></summary>Replaced Enum.max_by/3's DateTime comparator with a custom comparison function that safely handles nil timestamp values. code diff: ```diff @@ -514,8 +514,13 @@ updates |> Enum.group_by(& &1.device_id) |> Enum.map(fn {_device_id, grouped} -> - # Keep the update with the latest timestamp - Enum.max_by(grouped, & &1.timestamp, DateTime) + # 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) + end) ``` </details> ___ **Prevent a potential crash in <code>deduplicate_by_device_id</code> by providing a custom <br>comparator to <code>Enum.max_by</code> that correctly handles <code>nil</code> timestamp values.** [elixir/serviceradar_core/lib/serviceradar/identity/alias_events.ex [513-520]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-bc3743067ea774f59bc5665770f7110a2d6e90f6e1156a7717a1c287f8979d28R513-R520) ```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 - Enum.max_by(grouped, & &1.timestamp, DateTime) + # 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) + end) end) end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential `FunctionClauseError` when `Enum.max_by/3` receives a `nil` timestamp, which is permitted by the `device_update` type. The proposed fix makes the function robust by providing a custom comparator that correctly handles `nil` values. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>✅ <s>Prevent errors from missing tenant</s></summary> ___ <details><summary><b>Suggestion Impact:</b></summary>The commit added an explicit nil check for tenant_value and raises an ArgumentError with a clear message before calling TenantSchemas.schema_for_tenant/1, preventing downstream failures when the tenant cannot be resolved. code diff: ```diff + if is_nil(tenant_value) do + raise ArgumentError, + "Tenant could not be resolved - missing tenant identifier in options or attributes" + end ``` </details> ___ **In <code>resolve_tenant/2</code>, add a check to ensure a tenant identifier is found, and <br>raise an error if it is <code>nil</code> to prevent downstream failures.** [elixir/serviceradar_core/lib/serviceradar/edge/onboarding_packages.ex [448-456]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-e4fe8e19bc324416302bb4c962f57133b3f62eb82053766844d881c522a473e5R448-R456) ```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 "Tenant could not be resolved. Missing tenant identifier in options or attributes." + 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 identifies that a `nil` tenant identifier can cause a downstream error and proposes a valid guard clause. This improves robustness by failing early with a clear error message. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Use date-aware addition for validity</summary> ___ **Replace the manual date calculation with <code>DateTime.add/3</code> to correctly account for <br>leap years when setting the certificate's <code>not_after</code> date.** [elixir/serviceradar_core/lib/serviceradar/edge/tenant_ca/generator.ex [315-320]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-b48e4a9e1189da61e2a60e16f56fce81298d76b7cdab745107140fed3f6e48b4R315-R320) ```diff defp validity_period(years) do now = DateTime.utc_now() |> DateTime.truncate(:second) not_before = DateTime.add(now, -60, :second) # Allow 1 minute clock skew - not_after = DateTime.add(now, years * 365 * 24 * 60 * 60, :second) + not_after = DateTime.add(now, years, :year) {:ok, {not_before, not_after}} end ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies that the manual date calculation for certificate expiry does not account for leap years and proposes using `DateTime.add/3` for a more accurate and robust implementation. </details></details></td><td align=center>Low </td></tr><tr><td rowspan=3>General</td> <td> <details><summary>Only write operator config when path set</summary> ___ **Add a guard to ensure <code>WriteOperatorConfig()</code> is only called when <br><code>operatorConfigPath</code> is not empty, preventing a potential error when only <br><code>resolverPath</code> is set.** [cmd/data-services/main.go [117-130]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816R117-R130) ```diff if operatorConfigPath != "" || resolverPath != "" { natsAccountServer.SetResolverPaths(operatorConfigPath, resolverPath) log.Printf("NATS resolver paths configured: operator=%s resolver=%s", operatorConfigPath, resolverPath) - // If operator is already initialized, write the config now - // This ensures config files exist even when datasvc restarts with an existing operator - if operator != nil { + // Only write operator config if we have both an operator and a valid config path + if operator != nil && operatorConfigPath != "" { if err := natsAccountServer.WriteOperatorConfig(); err != nil { log.Printf("Warning: failed to write initial operator config: %v", err) } else { log.Printf("Wrote initial operator config to %s", operatorConfigPath) } } } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=6 --> <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: This suggestion correctly identifies a potential panic by adding a necessary check for a non-empty `operatorConfigPath` before attempting to write a file, thus preventing a runtime error. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Refactor duplicated code for clarity</summary> ___ **Refactor the duplicated <code>server.Stop</code> call to a single location to handle cleanup <br>for all exit paths from the push loop, improving code maintainability.** [cmd/agent/main.go [231-245]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3R231-R245) ```diff case err := <-errChan: - if err != nil && !errors.Is(err, context.Canceled) { - stopCtx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second) - defer stopCancel() - if stopErr := server.Stop(stopCtx); stopErr != nil { - log.Error().Err(stopErr).Msg("Error stopping agent services") - } - return fmt.Errorf("push loop error: %w", err) - } - + // Always attempt to gracefully stop the server's services. stopCtx, stopCancel := context.WithTimeout(context.Background(), 10*time.Second) defer stopCancel() if stopErr := server.Stop(stopCtx); stopErr != nil { log.Error().Err(stopErr).Msg("Error stopping agent services") } + // If the error was not a cancellation, it's a real problem, so return it. + if err != nil && !errors.Is(err, context.Canceled) { + return fmt.Errorf("push loop error: %w", err) + } + ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=7 --> <details><summary>Suggestion importance[1-10]: 5</summary> __ Why: This suggestion correctly identifies and resolves duplicated code by refactoring the shutdown logic, which improves code maintainability and readability. </details></details></td><td align=center>Low </td></tr><tr><td> <details><summary>Simplify trailing JSON data check</summary> ___ **Replace the complex logic for detecting trailing JSON data with a simpler, more <br>idiomatic check using <code>dec.More()</code>.** [cmd/agent/main.go [133-137]](https://github.com/carverauto/serviceradar/pull/2230/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3R133-R137) ```diff - if err := dec.Decode(&struct{}{}); err == nil { + if dec.More() { return nil, fmt.Errorf("failed to parse config: %w", errConfigTrailingData) - } else if !errors.Is(err, io.EOF) { - return nil, fmt.Errorf("failed to parse config: %w", err) } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=8 --> <details><summary>Suggestion importance[1-10]: 4</summary> __ Why: The suggestion proposes using the idiomatic `dec.More()` function to check for trailing JSON data, which simplifies the code and improves readability. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!2637
No description provided.