Fix/analytics page #2639

Merged
mfreeman451 merged 4 commits from refs/pull/2639/head into testing 2026-01-09 06:53:51 +00:00
mfreeman451 commented 2026-01-09 05:10:19 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2233
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2233
Original created: 2026-01-09T05:10:19Z
Original updated: 2026-01-09T06:54:49Z
Original head: carverauto/serviceradar:fix/analytics-page
Original base: testing
Original merged: 2026-01-09T06:53:51Z 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, Refactoring


Description

  • Major architectural refactoring from poller-based to gateway-based system: Comprehensive terminology and implementation changes throughout the codebase replacing poller with gateway

  • Multi-tenant gateway push architecture: Refactored sync service to support push-first communication model with gateway clients replacing pull-based gRPC methods

  • Gateway monitoring and status management: New comprehensive implementation for gateway health checks, offline/recovery detection, alert handling, and streaming status report support

  • NATS bootstrap and account management: Added CLI implementation for NATS bootstrap functionality with operator, system account, and platform account generation for multi-tenant routing

  • Protobuf definitions update: Refactored protobuf messages from poller to gateway architecture with new agent-gateway communication types and service definitions

  • Edge onboarding enhancements: Updated edge onboarding to support gateway terminology and added EdgeOnboardingComponentTypeSync component type support

  • Removed legacy poller infrastructure: Deleted poller-specific packages, CLI commands, and related implementations in favor of gateway-based approach


Diagram Walkthrough

flowchart LR
  A["Legacy Poller<br/>Architecture"] -->|"Refactor to"| B["Gateway-Based<br/>Architecture"]
  B -->|"Implements"| C["Push-First<br/>Communication"]
  B -->|"Adds"| D["Multi-Tenant<br/>Support"]
  B -->|"Includes"| E["Gateway Monitoring<br/>& Status Mgmt"]
  C -->|"Replaces"| F["Pull-Based<br/>gRPC Methods"]
  D -->|"Uses"| G["NATS Account<br/>Management"]
  E -->|"Provides"| H["Health Checks &<br/>Alert Handling"]

File Walkthrough

Relevant files
Enhancement
4 files
monitoring.pb.go
Refactor protobuf definitions from poller to gateway architecture

proto/monitoring.pb.go

  • Renamed PollerId field to GatewayId across multiple message types
    (StatusRequest, ResultsRequest, StatusResponse, ResultsResponse)
  • Removed deprecated PollerStatusRequest, PollerStatusResponse, and
    ServiceStatus message types
  • Replaced PollerStatusChunk with new GatewayStatusRequest,
    GatewayStatusResponse, and GatewayStatusChunk types
  • Added new GatewayServiceStatus message type with tenant-related fields
    (TenantId, TenantSlug)
  • Added new agent-gateway communication messages: AgentHelloRequest,
    AgentHelloResponse, AgentConfigRequest, AgentConfigResponse,
    AgentCheckConfig
  • Updated service definitions from PollerService to AgentGatewayService
    with new RPC methods
+1039/-404
nats_bootstrap.go
Add comprehensive NATS bootstrap CLI implementation           

pkg/cli/nats_bootstrap.go

  • New file implementing NATS bootstrap functionality for ServiceRadar
    CLI
  • Provides handlers for nats-bootstrap and admin nats subcommands with
    support for token-based and local bootstrap modes
  • Implements NATS operator, system account, and platform account
    generation and configuration
  • Includes verification mode to validate existing NATS bootstrap
    configurations
  • Supports multi-tenant routing with tenant ID and slug fields
+961/-0 
service.go
Multi-tenant gateway push architecture with dynamic config

pkg/sync/service.go

  • Refactored sync service to support multi-tenant architecture with
    gateway push-first communication model
  • Removed KV client and gRPC client dependencies; added gateway client
    for push-based results and status reporting
  • Implemented per-source discovery scheduling with interval tracking and
    per-tenant configuration management
  • Added gateway enrollment, config polling, and heartbeat loops for
    dynamic configuration updates
  • Deprecated pull-based GetResults and StreamResults gRPC methods in
    favor of push-based gateway communication
+1258/-316
gateways.go
Gateway monitoring and status management implementation   

pkg/core/gateways.go

  • New comprehensive gateway monitoring and status management
    implementation with 1541 lines of core functionality
  • Implements gateway health checks, offline/recovery detection, and
    alert handling with event publishing to NATS
  • Adds streaming status report support for large datasets with chunk
    reassembly and service message processing
  • Includes device registration, service registry integration, and
    auto-registration of gateways/agents/checkers
+1541/-0
Documentation
1 files
main.go
Update API documentation terminology                                         

cmd/core/main.go

  • Updated API description from "service pollers" to "service gateways"
    to reflect architectural terminology change
+1/-1     
Configuration changes
3 files
prod.exs
Add Elixir production configuration                                           

elixir/serviceradar_agent_gateway/config/prod.exs

  • New production configuration file for Elixir agent-gateway module
  • Sets logger level to info for production environment
+4/-0     
prod.exs
Elixir production configuration setup                                       

elixir/serviceradar_core/config/prod.exs

  • Added new production configuration file for Elixir application
  • Sets logger level to info for production environment
+6/-0     
.gitkeep
Docker Compose credentials directory placeholder                 

docker/compose/creds/.gitkeep

  • Creates new empty placeholder file for credentials directory in Docker
    Compose setup
+1/-0     
Dependencies
2 files
nats_account.pb.go
NATS account management protobuf definitions                         

proto/nats_account.pb.go

  • Generated protobuf code for NATS account management service with 15
    message types and 1 enum
  • Defines account creation, user credential generation, JWT signing, and
    operator bootstrap operations
  • Includes resource limits, subject mappings, and permission management
    for tenant isolation
+1266/-0
nats_account_grpc.pb.go
NATS account service gRPC definitions                                       

proto/nats_account_grpc.pb.go

  • Auto-generated gRPC service definitions for NATSAccountService with
    six RPC methods
  • Implements stateless NATS JWT signing operations for operator
    bootstrap, tenant account creation, and user credentials
  • Includes client and server interfaces with full gRPC handler
    implementations and service registration
+376/-0 
Miscellaneous
1 files
mock_armis.go
Remove KVWriter from Armis mock interfaces                             

pkg/sync/integrations/armis/mock_armis.go

  • Updated mock generation command to remove KVWriter interface from
    mocked interfaces
  • Reflects removal of KV write functionality from Armis integration
+2/-40   
Refactoring
2 files
edge_onboarding.go
Poller to gateway terminology refactoring and sync support

pkg/core/edge_onboarding.go

  • Comprehensive refactoring renaming poller terminology to gateway
    throughout the file (100+ occurrences)
  • Updates type names, function names, variable names, and comments to
    reflect gateway-centric architecture
  • Adds support for EdgeOnboardingComponentTypeSync component type in
    package creation and validation
  • Updates KV configuration paths from config/pollers/ to
    config/gateways/ and related metadata keys
+208/-206
main.go
SNMP checker gateway terminology update                                   

cmd/checkers/snmp/main.go

  • Updates SNMP checker service instantiation from NewSNMPPollerService
    to NewSNMPGatewayService
  • Changes struct type from snmp.Poller to snmp.Gateway for consistency
    with gateway terminology
+1/-1     
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     
main.go +174/-74
build.rs +0/-1     
monitoring.proto +3/-26   
server.rs +2/-0     
README.md +2/-2     
monitoring.proto +2/-26   
server.rs +6/-6     
main.go +16/-2   
config.rs +26/-0   
grpc_server.rs +2/-2     
message_processor.rs +1/-0     
nats.rs +4/-0     
zen-consumer-with-otel.json +1/-0     
zen-consumer.json +1/-0     
app.go +1/-1     
config.json +4/-4     
config.json +4/-4     
BUILD.bazel +1/-0     
main.go +68/-0   
README.md +3/-3     
README.md +9/-12   
flowgger.toml +1/-0     
nats_output.rs +14/-0   
otel.toml +1/-0     
config.rs +13/-0   
nats_output.rs +7/-0     
setup.rs +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 
main.go +1/-1     
config.rs +21/-0   
main.rs +23/-3   
docker-compose.elx.yml +109/-0 
docker-compose.spiffe.yml +8/-158 
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 
Additional files not shown

Imported from GitHub pull request. Original GitHub pull request: #2233 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2233 Original created: 2026-01-09T05:10:19Z Original updated: 2026-01-09T06:54:49Z Original head: carverauto/serviceradar:fix/analytics-page Original base: testing Original merged: 2026-01-09T06:53:51Z 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, Refactoring ___ ### **Description** - **Major architectural refactoring from poller-based to gateway-based system**: Comprehensive terminology and implementation changes throughout the codebase replacing `poller` with `gateway` - **Multi-tenant gateway push architecture**: Refactored sync service to support push-first communication model with gateway clients replacing pull-based gRPC methods - **Gateway monitoring and status management**: New comprehensive implementation for gateway health checks, offline/recovery detection, alert handling, and streaming status report support - **NATS bootstrap and account management**: Added CLI implementation for NATS bootstrap functionality with operator, system account, and platform account generation for multi-tenant routing - **Protobuf definitions update**: Refactored protobuf messages from poller to gateway architecture with new agent-gateway communication types and service definitions - **Edge onboarding enhancements**: Updated edge onboarding to support gateway terminology and added `EdgeOnboardingComponentTypeSync` component type support - **Removed legacy poller infrastructure**: Deleted poller-specific packages, CLI commands, and related implementations in favor of gateway-based approach ___ ### Diagram Walkthrough ```mermaid flowchart LR A["Legacy Poller<br/>Architecture"] -->|"Refactor to"| B["Gateway-Based<br/>Architecture"] B -->|"Implements"| C["Push-First<br/>Communication"] B -->|"Adds"| D["Multi-Tenant<br/>Support"] B -->|"Includes"| E["Gateway Monitoring<br/>& Status Mgmt"] C -->|"Replaces"| F["Pull-Based<br/>gRPC Methods"] D -->|"Uses"| G["NATS Account<br/>Management"] E -->|"Provides"| H["Health Checks &<br/>Alert Handling"] ``` <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>4 files</summary><table> <tr> <td> <details> <summary><strong>monitoring.pb.go</strong><dd><code>Refactor protobuf definitions from poller to gateway architecture</code></dd></summary> <hr> proto/monitoring.pb.go <ul><li>Renamed <code>PollerId</code> field to <code>GatewayId</code> across multiple message types <br>(<code>StatusRequest</code>, <code>ResultsRequest</code>, <code>StatusResponse</code>, <code>ResultsResponse</code>)<br> <li> Removed deprecated <code>PollerStatusRequest</code>, <code>PollerStatusResponse</code>, and <br><code>ServiceStatus</code> message types<br> <li> Replaced <code>PollerStatusChunk</code> with new <code>GatewayStatusRequest</code>, <br><code>GatewayStatusResponse</code>, and <code>GatewayStatusChunk</code> types<br> <li> Added new <code>GatewayServiceStatus</code> message type with tenant-related fields <br>(<code>TenantId</code>, <code>TenantSlug</code>)<br> <li> Added new agent-gateway communication messages: <code>AgentHelloRequest</code>, <br><code>AgentHelloResponse</code>, <code>AgentConfigRequest</code>, <code>AgentConfigResponse</code>, <br><code>AgentCheckConfig</code><br> <li> Updated service definitions from <code>PollerService</code> to <code>AgentGatewayService</code> <br>with new RPC methods</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4">+1039/-404</a></td> </tr> <tr> <td> <details> <summary><strong>nats_bootstrap.go</strong><dd><code>Add comprehensive NATS bootstrap CLI implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/cli/nats_bootstrap.go <ul><li>New file implementing NATS bootstrap functionality for ServiceRadar <br>CLI<br> <li> Provides handlers for <code>nats-bootstrap</code> and <code>admin nats</code> subcommands with <br>support for token-based and local bootstrap modes<br> <li> Implements NATS operator, system account, and platform account <br>generation and configuration<br> <li> Includes verification mode to validate existing NATS bootstrap <br>configurations<br> <li> Supports multi-tenant routing with tenant ID and slug fields</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457">+961/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>service.go</strong><dd><code>Multi-tenant gateway push architecture with dynamic config</code></dd></summary> <hr> pkg/sync/service.go <ul><li>Refactored sync service to support multi-tenant architecture with <br>gateway push-first communication model<br> <li> Removed KV client and gRPC client dependencies; added gateway client <br>for push-based results and status reporting<br> <li> Implemented per-source discovery scheduling with interval tracking and <br>per-tenant configuration management<br> <li> Added gateway enrollment, config polling, and heartbeat loops for <br>dynamic configuration updates<br> <li> Deprecated pull-based <code>GetResults</code> and <code>StreamResults</code> gRPC methods in <br>favor of push-based gateway communication</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4ea5a5b3d811fa94c6a84c71c0ddb920ce177a376d13cb776f05dd6017c4c7e">+1258/-316</a></td> </tr> <tr> <td> <details> <summary><strong>gateways.go</strong><dd><code>Gateway monitoring and status management implementation</code>&nbsp; &nbsp; </dd></summary> <hr> pkg/core/gateways.go <ul><li>New comprehensive gateway monitoring and status management <br>implementation with 1541 lines of core functionality<br> <li> Implements gateway health checks, offline/recovery detection, and <br>alert handling with event publishing to NATS<br> <li> Adds streaming status report support for large datasets with chunk <br>reassembly and service message processing<br> <li> Includes device registration, service registry integration, and <br>auto-registration of gateways/agents/checkers</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0">+1541/-0</a></td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>Update API documentation terminology</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/core/main.go <ul><li>Updated API description from "service pollers" to "service gateways" <br>to reflect architectural terminology change</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-4ab3fd1d4debc53dd2499d94a0f60c648fdae4235dd1e3678095a975f5bb434a">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>3 files</summary><table> <tr> <td> <details> <summary><strong>prod.exs</strong><dd><code>Add Elixir production configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_agent_gateway/config/prod.exs <ul><li>New production configuration file for Elixir agent-gateway module<br> <li> Sets logger level to <code>info</code> for production environment</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-5fdc859dfda96a02326392f61218325913f22f9f0c75a1276ee940d5db9fc62b">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>prod.exs</strong><dd><code>Elixir production configuration setup</code>&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/config/prod.exs <ul><li>Added new production configuration file for Elixir application<br> <li> Sets logger level to <code>info</code> for production environment</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-4fe837307f8710c783ecb9ae7595bb7bb9ec37d8522248bd081fa256803c3e92">+6/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>.gitkeep</strong><dd><code>Docker Compose credentials directory placeholder</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> docker/compose/creds/.gitkeep <ul><li>Creates new empty placeholder file for credentials directory in Docker <br>Compose setup</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-d72c41aab2d6f2c230a4340dfefe7917cdd12bed942c825aa0d4c9875a637bac">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Dependencies</strong></td><td><details><summary>2 files</summary><table> <tr> <td> <details> <summary><strong>nats_account.pb.go</strong><dd><code>NATS account management protobuf definitions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> proto/nats_account.pb.go <ul><li>Generated protobuf code for NATS account management service with 15 <br>message types and 1 enum<br> <li> Defines account creation, user credential generation, JWT signing, and <br>operator bootstrap operations<br> <li> Includes resource limits, subject mappings, and permission management <br>for tenant isolation</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-49eb93c28e2d86d8dcf4ee78fd24bed498cf3fcfaa8e49849a36e70980420087">+1266/-0</a></td> </tr> <tr> <td> <details> <summary><strong>nats_account_grpc.pb.go</strong><dd><code>NATS account service gRPC definitions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> proto/nats_account_grpc.pb.go <ul><li>Auto-generated gRPC service definitions for <code>NATSAccountService</code> with <br>six RPC methods<br> <li> Implements stateless NATS JWT signing operations for operator <br>bootstrap, tenant account creation, and user credentials<br> <li> Includes client and server interfaces with full gRPC handler <br>implementations and service registration</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-af98ae5f073d25a2ea0a044c996542fc65d215f6a70f14a9fba0447d87b34e11">+376/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Miscellaneous</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>mock_armis.go</strong><dd><code>Remove KVWriter from Armis mock interfaces</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/sync/integrations/armis/mock_armis.go <ul><li>Updated mock generation command to remove <code>KVWriter</code> interface from <br>mocked interfaces<br> <li> Reflects removal of KV write functionality from Armis integration</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-54fcd2e5cc009f705d49f5acabba4c8bf66841758c70ea6b8474f7e9318e8e46">+2/-40</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Refactoring</strong></td><td><details><summary>2 files</summary><table> <tr> <td> <details> <summary><strong>edge_onboarding.go</strong><dd><code>Poller to gateway terminology refactoring and sync support</code></dd></summary> <hr> pkg/core/edge_onboarding.go <ul><li>Comprehensive refactoring renaming <code>poller</code> terminology to <code>gateway</code> <br>throughout the file (100+ occurrences)<br> <li> Updates type names, function names, variable names, and comments to <br>reflect gateway-centric architecture<br> <li> Adds support for <code>EdgeOnboardingComponentTypeSync</code> component type in <br>package creation and validation<br> <li> Updates KV configuration paths from <code>config/pollers/</code> to <br><code>config/gateways/</code> and related metadata keys</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-85874e3c4bdcc9110db09909f10648d44cdee554b26c987f910502321eb20b5c">+208/-206</a></td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>SNMP checker gateway terminology update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/checkers/snmp/main.go <ul><li>Updates SNMP checker service instantiation from <code>NewSNMPPollerService</code> <br>to <code>NewSNMPGatewayService</code><br> <li> Changes struct type from <code>snmp.Poller</code> to <code>snmp.Gateway</code> for consistency <br>with gateway terminology</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-f25402eade63525184cb5e7437accff93c7b9338eebe81add6dc5f2a9eb12550">+1/-1</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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/files#diff-a54ff182c7e8acf56acfd6e4b9c3ff41e2c41a31c9b211b2deb9df75d9a478f9">+177/-11</a></td> </tr> <tr> <td><strong>INSTALL.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/files#diff-86ec281f99363b6b6eb1f49e21d83b7eeca93a35b552b9f305fffc6855e38ccd">+124/-49</a></td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/files#diff-5b1bc8fe77422534739bdd3a38dc20d2634a86c171265c34e1b5d0c5a61b6bab">+5/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3">+174/-74</a></td> </tr> <tr> <td><strong>build.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/files#diff-b56f709f4a0a3db694f2124353908318631f23e20b7846bc4b8ee869e2e0632a">+3/-26</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>server.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-bce0f4ca6548712f224b73816825d28e831acbbff7dbed3c98671ed50f65d028">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/files#diff-9faf6025eb0d3d38383f5b7ad2b733abeb38454d5e4de3e83994e94b12d87a50">+2/-26</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>server.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-2c4395fee16396339c3eea518ad9bec739174c67c9cedf62e6848c17136dd33e">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-ed4d81d29a7267f93fd77e17993fd3491b9ef6ded18490b4514d10ed1d803bc2">+16/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-05038f3867985e757de9027609950e682bad6d1992dac6acd7c28962a3c65dc4">+26/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>grpc_server.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-e4564a93f6cf84ff91cd3d8141fc9272ec9b4ec19defd107afa42be01fcfed5b">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>message_processor.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-9fcbc5358a9009e60a8cd22d21e5a9ea652787c727732d0b869e0865495114c3">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>nats.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-97f7335def0ad5d644b594a1076ae2d7080b11259cbb8de22c7946cc8e4b39f8">+4/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer-with-otel.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/files#diff-4d308af9802a93a0f656e8c02a3b5fcd8991407bb18360f087470db74e1f9524">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>app.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-4ad8a289575edf3b163088617b7a40ae1305c29ced0c7d59b3751c57d6938072">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/files#diff-c62c0139ebdb337369f4067567cd2c52b8e7decb3ddfabc77f9f67b2f6e5789c">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-5e7731adfb877918cd65d9d5531621312496450fd550fea2682efca4ca8fe816">+68/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/files#diff-af9f49f931e282dca53d1f0521b036d222fe671f77e61a876a84cf4c6d7cca4d">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>nats_output.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-a82e2e4d413539bf0b414b5629665b19648447523994cba639c4d1238aa5a0c1">+14/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.toml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-c64b9ace832b8ea57a2be62f84166e03bb1904882635d444ec76a880cdf14cc0">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-abbaec651da3d6af96b482e0f77bb909b65dbe0cabd78b5803769cc9dab0a1b0">+13/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>nats_output.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-6b585ea3564a481174e04da1270e2e13edd4e2b980d02a2652d6d21e6d82a498">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>setup.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-3891f667deb20fd26e296d3e2742c57378d3764fe1743118e612465ae360391f">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/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/2233/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/2233/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/2233/files#diff-78dc6bc53f1c760c66f43ff5f486bfe78a65bee8b2e0d4862293ec0892da2b29">+0/-123</a>&nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-bc6eeb1b05bcb9179525e32fac1de9926b5823ec3504be546ab10c5c9740f544">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-c89b88ba4d2bf0a054d0ba69a672a92c30140b8d19503d67b980a218ffe3106d">+21/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-33b655d8730ae3e9c844ee280787d11f1b0d5343119188273f89558805f814ba">+23/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>docker-compose.elx.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/files#diff-603fd9e7d40841d174f26b95d0cb0c9537430bf3f7a5da3ccbba4ea3d8ac66c9">+8/-158</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/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/2233/files#diff-d7aec89d87f4cc98f4d6935e49a8f6ce571bc6dda254d894e93b60922f3a775f">+0/-204</a>&nbsp; </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2233/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-09 05:11:49 +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/2233#issuecomment-3727220550
Original created: 2026-01-09T05:11:49Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Tenant spoofing

Description: The newly added gateway push messages include client-populated identity/routing fields
(e.g., tenant_id, tenant_slug, and gateway_id in
GatewayStatusRequest/GatewayStatusChunk/GatewayServiceStatus), which could enable
cross-tenant data injection or routing spoofing if the receiving gateway/control-plane
code trusts these fields instead of deriving tenant and gateway identity from
authenticated context (e.g., mTLS cert claims) and enforcing authorization.
monitoring.pb.go [826-1127]

Referred Code
// GatewayStatusRequest is sent by agents to push their status to the gateway.
type GatewayStatusRequest struct {
	state         protoimpl.MessageState  `protogen:"open.v1"`
	Services      []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"`
	GatewayId     string                  `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status
	AgentId       string                  `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"`       // Agent sending this status
	Timestamp     int64                   `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
	Partition     string                  `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"`                     // Partition identifier
	SourceIp      string                  `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`       // Host IP where agent is running
	KvStoreId     string                  `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"`  // KV store identifier this agent is using
	TenantId      string                  `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`       // Tenant UUID for multi-tenant routing
	TenantSlug    string                  `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *GatewayStatusRequest) Reset() {
	*x = GatewayStatusRequest{}
	mi := &file_monitoring_proto_msgTypes[9]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)



 ... (clipped 281 lines)
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: Secure Error Handling

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

Status: Passed

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

Generic: Secure Logging Practices

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

Status: Passed

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

Generic: Comprehensive Audit Trails

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

Status:
Audit logging unclear: The PR introduces new gateway/agent status and config RPC flows but the diff does not show
whether these critical actions are audit-logged with actor identity, timestamp, action,
and outcome.

Referred Code
// GatewayStatusRequest is sent by agents to push their status to the gateway.
type GatewayStatusRequest struct {
	state         protoimpl.MessageState  `protogen:"open.v1"`
	Services      []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"`
	GatewayId     string                  `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status
	AgentId       string                  `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"`       // Agent sending this status
	Timestamp     int64                   `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
	Partition     string                  `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"`                     // Partition identifier
	SourceIp      string                  `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`       // Host IP where agent is running
	KvStoreId     string                  `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"`  // KV store identifier this agent is using
	TenantId      string                  `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`       // Tenant UUID for multi-tenant routing
	TenantSlug    string                  `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *GatewayStatusRequest) Reset() {
	*x = GatewayStatusRequest{}
	mi := &file_monitoring_proto_msgTypes[9]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)



 ... (clipped 90 lines)

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

Generic: Robust Error Handling and Edge Case Management

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

Status:
Error handling unknown: New request/response message types and gateway RPC endpoints are added, but the diff does
not show server/client-side handling for invalid payloads, missing required fields,
partial stream chunks, or other edge cases.

Referred Code
// GatewayStatusRequest is sent by agents to push their status to the gateway.
type GatewayStatusRequest struct {
	state         protoimpl.MessageState  `protogen:"open.v1"`
	Services      []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"`
	GatewayId     string                  `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status
	AgentId       string                  `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"`       // Agent sending this status
	Timestamp     int64                   `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
	Partition     string                  `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"`                     // Partition identifier
	SourceIp      string                  `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`       // Host IP where agent is running
	KvStoreId     string                  `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"`  // KV store identifier this agent is using
	TenantId      string                  `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`       // Tenant UUID for multi-tenant routing
	TenantSlug    string                  `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *GatewayStatusRequest) Reset() {
	*x = GatewayStatusRequest{}
	mi := &file_monitoring_proto_msgTypes[9]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)



 ... (clipped 188 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:
Input validation unseen: The PR adds externally-supplied identifiers and routing fields (e.g., tenant_id,
tenant_slug, source_ip, gateway_id) but the diff does not show validation/sanitization or
authorization checks for these inputs.

Referred Code
// GatewayStatusRequest is sent by agents to push their status to the gateway.
type GatewayStatusRequest struct {
	state         protoimpl.MessageState  `protogen:"open.v1"`
	Services      []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"`
	GatewayId     string                  `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status
	AgentId       string                  `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"`       // Agent sending this status
	Timestamp     int64                   `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
	Partition     string                  `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"`                     // Partition identifier
	SourceIp      string                  `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"`       // Host IP where agent is running
	KvStoreId     string                  `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"`  // KV store identifier this agent is using
	TenantId      string                  `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`       // Tenant UUID for multi-tenant routing
	TenantSlug    string                  `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *GatewayStatusRequest) Reset() {
	*x = GatewayStatusRequest{}
	mi := &file_monitoring_proto_msgTypes[9]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)



 ... (clipped 151 lines)

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/2233#issuecomment-3727220550 Original created: 2026-01-09T05:11:49Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/98352f053456832fe882c405bb4f746e8ddbaa3a --> Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=1>⚪</td> <td><details><summary><strong>Tenant spoofing </strong></summary><br> <b>Description:</b> The newly added gateway push messages include client-populated identity/routing fields <br>(e.g., <code>tenant_id</code>, <code>tenant_slug</code>, and <code>gateway_id</code> in <br><code>GatewayStatusRequest</code>/<code>GatewayStatusChunk</code>/<code>GatewayServiceStatus</code>), which could enable <br>cross-tenant data injection or routing spoofing if the receiving gateway/control-plane <br>code trusts these fields instead of deriving tenant and gateway identity from <br>authenticated context (e.g., mTLS cert claims) and enforcing authorization.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2233/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4R826-R1127'>monitoring.pb.go [826-1127]</a></strong><br> <details open><summary>Referred Code</summary> ```go // GatewayStatusRequest is sent by agents to push their status to the gateway. type GatewayStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Services []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status AgentId string `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` // Agent sending this status Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Partition string `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"` // Partition identifier SourceIp string `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"` // Host IP where agent is running KvStoreId string `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"` // KV store identifier this agent is using TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` // Tenant UUID for multi-tenant routing TenantSlug string `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayStatusRequest) Reset() { *x = GatewayStatusRequest{} mi := &file_monitoring_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) ... (clipped 281 lines) ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary>🎫 <strong>No ticket provided </strong></summary> - [ ] Create ticket/issue <!-- /create_ticket --create_ticket=true --> </details></td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=3>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <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:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=3>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2233/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4R826-R936'><strong>Audit logging unclear</strong></a>: The PR introduces new gateway/agent status and config RPC flows but the diff does not show <br>whether these critical actions are audit-logged with actor identity, timestamp, action, <br>and outcome.<br> <details open><summary>Referred Code</summary> ```go // GatewayStatusRequest is sent by agents to push their status to the gateway. type GatewayStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Services []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status AgentId string `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` // Agent sending this status Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Partition string `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"` // Partition identifier SourceIp string `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"` // Host IP where agent is running KvStoreId string `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"` // KV store identifier this agent is using TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` // Tenant UUID for multi-tenant routing TenantSlug string `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayStatusRequest) Reset() { *x = GatewayStatusRequest{} mi := &file_monitoring_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) ... (clipped 90 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2233/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4R826-R1034'><strong>Error handling unknown</strong></a>: New request/response message types and gateway RPC endpoints are added, but the diff does <br>not show server/client-side handling for invalid payloads, missing required fields, <br>partial stream chunks, or other edge cases.<br> <details open><summary>Referred Code</summary> ```go // GatewayStatusRequest is sent by agents to push their status to the gateway. type GatewayStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Services []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status AgentId string `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` // Agent sending this status Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Partition string `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"` // Partition identifier SourceIp string `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"` // Host IP where agent is running KvStoreId string `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"` // KV store identifier this agent is using TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` // Tenant UUID for multi-tenant routing TenantSlug string `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayStatusRequest) Reset() { *x = GatewayStatusRequest{} mi := &file_monitoring_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) ... (clipped 188 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/2233/files#diff-4f7e955b42854cc9cf3fb063b95e58a04f36271e6a0c1cb42ea6d7953dd96cc4R826-R997'><strong>Input validation unseen</strong></a>: The PR adds externally-supplied identifiers and routing fields (e.g., <code>tenant_id</code>, <br><code>tenant_slug</code>, <code>source_ip</code>, <code>gateway_id</code>) but the diff does not show validation/sanitization or <br>authorization checks for these inputs.<br> <details open><summary>Referred Code</summary> ```go // GatewayStatusRequest is sent by agents to push their status to the gateway. type GatewayStatusRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Services []*GatewayServiceStatus `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` // Gateway receiving this status AgentId string `protobuf:"bytes,3,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` // Agent sending this status Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Partition string `protobuf:"bytes,5,opt,name=partition,proto3" json:"partition,omitempty"` // Partition identifier SourceIp string `protobuf:"bytes,6,opt,name=source_ip,json=sourceIp,proto3" json:"source_ip,omitempty"` // Host IP where agent is running KvStoreId string `protobuf:"bytes,7,opt,name=kv_store_id,json=kvStoreId,proto3" json:"kv_store_id,omitempty"` // KV store identifier this agent is using TenantId string `protobuf:"bytes,8,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` // Tenant UUID for multi-tenant routing TenantSlug string `protobuf:"bytes,9,opt,name=tenant_slug,json=tenantSlug,proto3" json:"tenant_slug,omitempty"` // Tenant slug for NATS subject prefixing unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *GatewayStatusRequest) Reset() { *x = GatewayStatusRequest{} mi := &file_monitoring_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) ... (clipped 151 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 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-09 05:13:16 +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/2233#issuecomment-3727223101
Original created: 2026-01-09T05:13:16Z

PR Code Suggestions

Latest suggestions up to 2e44e59

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Snapshot maps to avoid races

To prevent a race condition, create a deep copy of s.tenantSources under the
read lock before passing it to detectAndResetChangedIntervals.

pkg/sync/service.go [1433-1444]

 s.tenantMu.RLock()
-oldSources := s.tenantSources
+oldSources := make(map[string]map[string]*models.SourceConfig, len(s.tenantSources))
+for tenantID, sourceMap := range s.tenantSources {
+  copied := make(map[string]*models.SourceConfig, len(sourceMap))
+  for name, src := range sourceMap {
+    copied[name] = src
+  }
+  oldSources[tenantID] = copied
+}
 s.tenantMu.RUnlock()
 
 s.detectAndResetChangedIntervals(grouped, oldSources)
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical race condition that could cause the application to panic and provides a correct solution by creating a snapshot of the map while under lock.

High
Normalize tenant before schema lookup

Normalize the tenant to an ID using tenant_id_from/1 before calling
TenantSchemas.schema_for_tenant/1 to prevent potential crashes from incorrect
types.

web-ng/lib/serviceradar_web_ng/srql/ash_adapter.ex [646-652]

 defp normalize_tenant_for_resource(tenant, resource) do
+  tenant_id = tenant_id_from(tenant)
+
   case Ash.Resource.Info.multitenancy_strategy(resource) do
-    :context -> TenantSchemas.schema_for_tenant(tenant)
-    :attribute -> tenant_id_from(tenant)
-    _ -> tenant_id_from(tenant)
+    :context -> TenantSchemas.schema_for_tenant(tenant_id)
+    :attribute -> tenant_id
+    _ -> tenant_id
   end
 end
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential crash by passing an incorrect type to TenantSchemas.schema_for_tenant/1 and proposes a robust fix by normalizing the tenant representation first.

Medium
Safely convert decimals to integers

Safely convert Decimal values to integers by first rounding them down to avoid
crashes when the decimal has a fractional part.

web-ng/lib/serviceradar_web_ng_web/live/analytics_live/index.ex [409-432]

-defp extract_count_value(%Decimal{} = value), do: Decimal.to_integer(value)
+defp extract_count_value(%Decimal{} = value),
+  do: value |> Decimal.round(0, :down) |> Decimal.to_integer()
 
-defp parse_count_value(%Decimal{} = value), do: Decimal.to_integer(value)
+defp parse_count_value(%Decimal{} = value),
+  do: value |> Decimal.round(0, :down) |> Decimal.to_integer()
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential ArithmeticError crash when converting a Decimal with a fractional part to an integer and provides a safe conversion method.

Medium
Possible issue
Update modification timestamp on ingest

Explicitly update the modified_time attribute on the device changeset, in
addition to last_seen_time, to ensure it is updated on every ingest.

elixir/serviceradar_core/lib/serviceradar/inventory/sync_ingestor.ex [36-40]

 device
 |> Ash.Changeset.new()
 |> Ash.Changeset.change_attribute(:last_seen_time, timestamp)
+|> Ash.Changeset.change_attribute(:modified_time, timestamp)
 |> Ash.Changeset.for_update(:update, update_attrs)
 |> Ash.update(tenant: tenant_schema, actor: actor, authorize?: false)
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the PR stopped updating modified_time on device ingest, which could break logic relying on this timestamp, and provides a correct fix.

Medium
  • More

Previous suggestions

Suggestions up to commit 98352f0
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix race condition on sources
Suggestion Impact:The commit introduced snapshotLegacySources() to copy s.sources under a read lock, and updated runDiscovery (and related metrics) to use the snapshot instead of s.sources directly. It also applied the same snapshotting approach to the Armis update path.

code diff:

@@ -412,7 +412,8 @@
 
 	tenantIntegrations := s.snapshotTenantIntegrations()
 	if len(tenantIntegrations) == 0 {
-		allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", s.sources)
+		sourcesSnapshot := s.snapshotLegacySources()
+		allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", sourcesSnapshot)
 		s.updateResultsStore(allDeviceUpdates)
 
 		if err := s.pushResultsForTenant(ctx, "", "", allDeviceUpdates); err != nil {
@@ -420,7 +421,7 @@
 		}
 
 		totalDevices := countDevices(allDeviceUpdates)
-		s.metrics.RecordActiveIntegrations(len(s.sources))
+		s.metrics.RecordActiveIntegrations(len(sourcesSnapshot))
 		s.metrics.RecordTotalDevicesDiscovered(totalDevices)
 
 		s.logger.Info().
@@ -690,7 +691,8 @@
 	var updateErrors []error
 	tenantIntegrations := s.snapshotTenantIntegrations()
 	if len(tenantIntegrations) == 0 {
-		updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, "", s.sources)...)
+		sourcesSnapshot := s.snapshotLegacySources()
+		updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, "", sourcesSnapshot)...)
 	} else {
 		for tenantID, integrations := range tenantIntegrations {
 			updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, tenantID, integrations)...)
@@ -1428,6 +1430,10 @@
 	tenantIntegrations := make(map[string]map[string]Integration, len(grouped))
 	tenantResults := make(map[string]*StreamingResultsStore, len(grouped))
 
+	s.tenantMu.RLock()
+	oldSources := s.tenantSources
+	s.tenantMu.RUnlock()
+
 	// Read fallback values under lock
 	s.configMu.RLock()
 	agentID := s.config.AgentID
@@ -1435,7 +1441,7 @@
 	s.configMu.RUnlock()
 
 	// Detect interval changes and reset timers for affected sources
-	s.detectAndResetChangedIntervals(grouped)
+	s.detectAndResetChangedIntervals(grouped, oldSources)
 
 	for tenantID, sourceMap := range grouped {
 		integrations := make(map[string]Integration, len(sourceMap))
@@ -1470,11 +1476,10 @@
 
 // detectAndResetChangedIntervals compares old and new source configs,
 // resetting discovery timers for sources whose intervals have changed.
-func (s *SimpleSyncService) detectAndResetChangedIntervals(newSources map[string]map[string]*models.SourceConfig) {
-	s.tenantMu.RLock()
-	oldSources := s.tenantSources
-	s.tenantMu.RUnlock()
-
+func (s *SimpleSyncService) detectAndResetChangedIntervals(
+	newSources map[string]map[string]*models.SourceConfig,
+	oldSources map[string]map[string]*models.SourceConfig,
+) {
 	s.configMu.RLock()
 	globalInterval := s.config.GetEffectiveDiscoveryInterval(nil)
 	s.configMu.RUnlock()
@@ -1589,6 +1594,22 @@
 	return snapshot
 }
 
+func (s *SimpleSyncService) snapshotLegacySources() map[string]Integration {
+	s.configMu.RLock()
+	defer s.configMu.RUnlock()
+
+	if len(s.sources) == 0 {
+		return nil
+	}
+
+	snapshot := make(map[string]Integration, len(s.sources))
+	for name, integration := range s.sources {
+		snapshot[name] = integration
+	}
+
+	return snapshot
+}

To prevent a race condition in runDiscovery, create a snapshot of s.sources
under a read lock before using it.

pkg/sync/service.go [407-419]

 func (s *SimpleSyncService) runDiscovery(ctx context.Context) error {
 	start := time.Now()
 	s.logger.Info().
 		Time("started_at", start).
 		Msg("Starting discovery cycle")
 
 	tenantIntegrations := s.snapshotTenantIntegrations()
 	if len(tenantIntegrations) == 0 {
-		allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", s.sources)
+		sourcesSnapshot := s.snapshotLegacySources()
+		allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", sourcesSnapshot)
 		s.updateResultsStore(allDeviceUpdates)
 
 		if err := s.pushResultsForTenant(ctx, "", "", allDeviceUpdates); err != nil {
 			s.logger.Error().Err(err).Msg("Failed to push sync results to gateway")
 		}
 ...
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical race condition on the s.sources map, which could cause the application to panic, and proposes a valid solution.

High
Fix race condition on tenant sources
Suggestion Impact:The commit changed concurrency handling around tenant sources by taking a tenantMu read-lock in setTenantSources to snapshot the previous s.tenantSources ("oldSources") and passing that snapshot into detectAndResetChangedIntervals, removing the internal lock from detectAndResetChangedIntervals. This relates to the suggested race-condition concern, but it does not implement the suggested fix of holding a tenantMu write lock for the full duration of setTenantSources modifications.

code diff:

@@ -1428,6 +1430,10 @@
 	tenantIntegrations := make(map[string]map[string]Integration, len(grouped))
 	tenantResults := make(map[string]*StreamingResultsStore, len(grouped))
 
+	s.tenantMu.RLock()
+	oldSources := s.tenantSources
+	s.tenantMu.RUnlock()
+
 	// Read fallback values under lock
 	s.configMu.RLock()
 	agentID := s.config.AgentID
@@ -1435,7 +1441,7 @@
 	s.configMu.RUnlock()
 
 	// Detect interval changes and reset timers for affected sources
-	s.detectAndResetChangedIntervals(grouped)
+	s.detectAndResetChangedIntervals(grouped, oldSources)
 
 	for tenantID, sourceMap := range grouped {
 		integrations := make(map[string]Integration, len(sourceMap))
@@ -1470,11 +1476,10 @@
 
 // detectAndResetChangedIntervals compares old and new source configs,
 // resetting discovery timers for sources whose intervals have changed.
-func (s *SimpleSyncService) detectAndResetChangedIntervals(newSources map[string]map[string]*models.SourceConfig) {
-	s.tenantMu.RLock()
-	oldSources := s.tenantSources
-	s.tenantMu.RUnlock()
-
+func (s *SimpleSyncService) detectAndResetChangedIntervals(
+	newSources map[string]map[string]*models.SourceConfig,
+	oldSources map[string]map[string]*models.SourceConfig,
+) {

Fix a race condition in setTenantSources by acquiring a write lock on s.tenantMu
at the start of the function and holding it until all modifications are
complete.

pkg/sync/service.go [1426-1441]

 func (s *SimpleSyncService) setTenantSources(sources map[string]*models.SourceConfig, scope string) {
+	s.tenantMu.Lock()
+	defer s.tenantMu.Unlock()
+
 	grouped, slugs := s.groupSourcesByTenant(sources, scope)
 	tenantIntegrations := make(map[string]map[string]Integration, len(grouped))
 	tenantResults := make(map[string]*StreamingResultsStore, len(grouped))
 
 	// Read fallback values under lock
 	s.configMu.RLock()
 	agentID := s.config.AgentID
 	gatewayID := s.config.GatewayID
 	s.configMu.RUnlock()
 
 	// Detect interval changes and reset timers for affected sources
 	s.detectAndResetChangedIntervals(grouped)
 
 	for tenantID, sourceMap := range grouped {
 ...
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical race condition where s.tenantSources is read and then written without a continuous lock, which could lead to data corruption or panics.

High
Schedule daily cleanup tick
Suggestion Impact:Implemented the missing cleanupTicker case in the select loop, invoking cleanupUnknownGateways on each tick and logging failures.

code diff:

+		case <-cleanupTicker.C:
+			if err := s.cleanupUnknownGateways(ctx); err != nil {
+				s.logger.Error().
+					Err(err).
+					Msg("Daily cleanup of unknown gateways failed")
+			}

Add a case for cleanupTicker in the select loop to ensure the daily cleanup of
unknown gateways is executed as intended.

pkg/core/gateways.go [44-73]

 func (s *Server) MonitorGateways(ctx context.Context) {
     ticker := time.NewTicker(monitorInterval)
     defer ticker.Stop()
 
     cleanupTicker := time.NewTicker(dailyCleanupInterval)
     defer cleanupTicker.Stop()
 
     if err := s.checkGatewayStatus(ctx); err != nil {
         s.logger.Error().
             Err(err).
             Msg("Initial state check failed")
     }
 
     for {
         select {
         case <-ctx.Done():
             return
         case <-s.ShutdownChan:
             return
         case <-ticker.C:
             s.handleMonitorTick(ctx)
+        case <-cleanupTicker.C:
+            if err := s.cleanupUnknownGateways(ctx); err != nil {
+                s.logger.Error().
+                    Err(err).
+                    Msg("Daily cleanup of unknown gateways failed")
+            }
         }
     }
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a significant bug where the cleanupTicker is initialized but never used, causing a critical daily cleanup task to be skipped. This fix is essential for the intended functionality.

High
Prevent potential goroutine leak on cancellation

Replace context.WithoutCancel(ctx) with the original ctx in the background
goroutine to prevent potential goroutine leaks if the request is cancelled.

pkg/core/gateways.go [619-620]

 // Run in a separate goroutine to not block the main status report flow.
-// Create a detached context but preserve trace information
-detachedCtx := context.WithoutCancel(ctx)
-s.registerServiceOrCoreDevice(detachedCtx, req.GatewayId, req.Partition, normalizedIP, services, now)
+s.registerServiceOrCoreDevice(ctx, req.GatewayId, req.Partition, normalizedIP, services, now)
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential goroutine leak due to using a detached context. While the original code's intent was to ensure registration completes, respecting context cancellation is a more robust and safer pattern to prevent resource exhaustion.

Medium
Persist gateway recovery status to DB

Persist the gateway's recovered status to the database by queueing a status
update, ensuring the state is not lost on restart.

pkg/core/gateways.go [148-149]

-// Simply clear the alert flag and mark as healthy - PushStatus handles proper recovery events
+// Queue the status update to persist the recovery
+s.queueGatewayStatusUpdate(ps.GatewayID, true, ps.LastSeen, ps.FirstSeen)
+
+// Also update the in-memory cache state
 ps.AlertSent = false
 ps.IsHealthy = true
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the gateway recovery status is only updated in-memory and not persisted, which is a bug in the backup recovery mechanism. Applying this fix ensures state consistency with the database.

Medium
Refactor to prevent potential deadlocks

Refactor runDiscoveryForIntegrations to avoid potential deadlocks by acquiring
all necessary locks at once, retrieving data, and then releasing them.

pkg/sync/service.go [483-519]

 func (s *SimpleSyncService) runDiscoveryForIntegrations(
 	ctx context.Context,
 	tenantID string,
 	tenantSlug string,
 	integrations map[string]Integration,
 ) (map[string][]*models.DeviceUpdate, []error) {
 ...
 	for sourceName, integration := range integrations {
-		// Get source config to check per-source interval
-		sourceConfig := s.getSourceConfig(tenantID, sourceName)
+		sourceConfig, isDue, interval := s.checkIfSourceIsDue(tenantID, sourceName)
 
-		// Check if this source is due for discovery based on its interval
-		if !s.isSourceDueForDiscovery(tenantID, sourceName, sourceConfig) {
-...
+		if !isDue {
+			s.logger.Debug().
+				Str("source", sourceName).
+				Str("tenant_id", tenantID).
+				Dur("interval", interval).
+				Msg("Skipping discovery for source - not due yet")
+			continue
 		}
 
 		logEvent := s.logger.Info().Str("source", sourceName)
-...
-		s.configMu.RLock()
-		interval := s.config.GetEffectiveDiscoveryInterval(sourceConfig)
-		s.configMu.RUnlock()
+		if tenantID != "" {
+			logEvent = logEvent.Str("tenant_id", tenantID)
+		}
 		logEvent = logEvent.Dur("interval", interval)
 		logEvent.Msg("Running discovery for source")
 ...
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential deadlock scenario due to inconsistent lock ordering, and the proposed refactoring improves code safety and maintainability.

Low
Distinguish not-found from query errors

Refine error handling to specifically check for a "not found" error
(db.ErrNotFound) instead of a generic query failure when determining if a
gateway is new.

pkg/core/gateways.go [445-454]

 existingStatus, err := s.DB.GetGatewayStatus(ctx, gatewayID)
-if err != nil && !errors.Is(err, db.ErrFailedToQuery) {
-    return fmt.Errorf("failed to check gateway existence: %w", err)
-}
-
 if err != nil {
-    gatewayStatus.FirstSeen = timestamp
+    if errors.Is(err, db.ErrNotFound) {
+        gatewayStatus.FirstSeen = timestamp
+    } else {
+        return fmt.Errorf("failed to check gateway existence: %w", err)
+    }
 } else {
     gatewayStatus.FirstSeen = existingStatus.FirstSeen
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that the error handling is too broad, treating all query failures as a "not found" case. Using a specific db.ErrNotFound check improves robustness by distinguishing between a missing record and an actual database error.

Low
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2233#issuecomment-3727223101 Original created: 2026-01-09T05:13:16Z --- ## PR Code Suggestions ✨ <!-- 2e44e59 --> Latest suggestions up to 2e44e59 <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=3>Incremental <sup><a href='https://qodo-merge-docs.qodo.ai/core-abilities/incremental_update/'>[*]</a></sup></td> <td> <details><summary>Snapshot maps to avoid races</summary> ___ **To prevent a race condition, create a deep copy of <code>s.tenantSources</code> under the <br>read lock before passing it to <code>detectAndResetChangedIntervals</code>.** [pkg/sync/service.go [1433-1444]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4ea5a5b3d811fa94c6a84c71c0ddb920ce177a376d13cb776f05dd6017c4c7eR1433-R1444) ```diff s.tenantMu.RLock() -oldSources := s.tenantSources +oldSources := make(map[string]map[string]*models.SourceConfig, len(s.tenantSources)) +for tenantID, sourceMap := range s.tenantSources { + copied := make(map[string]*models.SourceConfig, len(sourceMap)) + for name, src := range sourceMap { + copied[name] = src + } + oldSources[tenantID] = copied +} s.tenantMu.RUnlock() s.detectAndResetChangedIntervals(grouped, oldSources) ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=0 --> <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical race condition that could cause the application to panic and provides a correct solution by creating a snapshot of the map while under lock. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Normalize tenant before schema lookup</summary> ___ **Normalize the <code>tenant</code> to an ID using <code>tenant_id_from/1</code> before calling <br><code>TenantSchemas.schema_for_tenant/1</code> to prevent potential crashes from incorrect <br>types.** [web-ng/lib/serviceradar_web_ng/srql/ash_adapter.ex [646-652]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4206b4f7d4604e08471361ad7ddae4b6dbb75f67239cb134d20644633f5eb8bR646-R652) ```diff defp normalize_tenant_for_resource(tenant, resource) do + tenant_id = tenant_id_from(tenant) + case Ash.Resource.Info.multitenancy_strategy(resource) do - :context -> TenantSchemas.schema_for_tenant(tenant) - :attribute -> tenant_id_from(tenant) - _ -> tenant_id_from(tenant) + :context -> TenantSchemas.schema_for_tenant(tenant_id) + :attribute -> tenant_id + _ -> tenant_id end end ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=1 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential crash by passing an incorrect type to `TenantSchemas.schema_for_tenant/1` and proposes a robust fix by normalizing the tenant representation first. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Safely convert decimals to integers</summary> ___ **Safely convert <code>Decimal</code> values to integers by first rounding them down to avoid <br>crashes when the decimal has a fractional part.** [web-ng/lib/serviceradar_web_ng_web/live/analytics_live/index.ex [409-432]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-35fb1715e171ac9f3240e9c02d2170b636acbb369952d58d352e1f75c91f82e3R409-R432) ```diff -defp extract_count_value(%Decimal{} = value), do: Decimal.to_integer(value) +defp extract_count_value(%Decimal{} = value), + do: value |> Decimal.round(0, :down) |> Decimal.to_integer() -defp parse_count_value(%Decimal{} = value), do: Decimal.to_integer(value) +defp parse_count_value(%Decimal{} = value), + do: value |> Decimal.round(0, :down) |> Decimal.to_integer() ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=2 --> <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly identifies a potential `ArithmeticError` crash when converting a `Decimal` with a fractional part to an integer and provides a safe conversion method. </details></details></td><td align=center>Medium </td></tr><tr><td rowspan=1>Possible issue</td> <td> <details><summary>Update modification timestamp on ingest</summary> ___ **Explicitly update the <code>modified_time</code> attribute on the device changeset, in <br>addition to <code>last_seen_time</code>, to ensure it is updated on every ingest.** [elixir/serviceradar_core/lib/serviceradar/inventory/sync_ingestor.ex [36-40]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-fdf70a310cef758f735fae943c2a33bc7f851a1c3d1ba66499e911fd2bc5611aR36-R40) ```diff device |> Ash.Changeset.new() |> Ash.Changeset.change_attribute(:last_seen_time, timestamp) +|> Ash.Changeset.change_attribute(:modified_time, timestamp) |> Ash.Changeset.for_update(:update, update_attrs) |> Ash.update(tenant: tenant_schema, actor: actor, authorize?: false) ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=3 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies that the PR stopped updating `modified_time` on device ingest, which could break logic relying on this timestamp, and provides a correct fix. </details></details></td><td align=center>Medium </td></tr> <tr><td align="center" colspan="2"> - [ ] More <!-- /improve --more_suggestions=true --> </td><td></td></tr></tbody></table> ___ #### Previous suggestions <details><summary>✅ Suggestions up to commit 98352f0</summary> <br><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=7>Possible issue</td> <td> <details><summary>✅ <s>Fix race condition on sources</s></summary> ___ <details><summary><b>Suggestion Impact:</b></summary>The commit introduced snapshotLegacySources() to copy s.sources under a read lock, and updated runDiscovery (and related metrics) to use the snapshot instead of s.sources directly. It also applied the same snapshotting approach to the Armis update path. code diff: ```diff @@ -412,7 +412,8 @@ tenantIntegrations := s.snapshotTenantIntegrations() if len(tenantIntegrations) == 0 { - allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", s.sources) + sourcesSnapshot := s.snapshotLegacySources() + allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", sourcesSnapshot) s.updateResultsStore(allDeviceUpdates) if err := s.pushResultsForTenant(ctx, "", "", allDeviceUpdates); err != nil { @@ -420,7 +421,7 @@ } totalDevices := countDevices(allDeviceUpdates) - s.metrics.RecordActiveIntegrations(len(s.sources)) + s.metrics.RecordActiveIntegrations(len(sourcesSnapshot)) s.metrics.RecordTotalDevicesDiscovered(totalDevices) s.logger.Info(). @@ -690,7 +691,8 @@ var updateErrors []error tenantIntegrations := s.snapshotTenantIntegrations() if len(tenantIntegrations) == 0 { - updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, "", s.sources)...) + sourcesSnapshot := s.snapshotLegacySources() + updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, "", sourcesSnapshot)...) } else { for tenantID, integrations := range tenantIntegrations { updateErrors = append(updateErrors, s.runArmisUpdatesForIntegrations(ctx, tenantID, integrations)...) @@ -1428,6 +1430,10 @@ tenantIntegrations := make(map[string]map[string]Integration, len(grouped)) tenantResults := make(map[string]*StreamingResultsStore, len(grouped)) + s.tenantMu.RLock() + oldSources := s.tenantSources + s.tenantMu.RUnlock() + // Read fallback values under lock s.configMu.RLock() agentID := s.config.AgentID @@ -1435,7 +1441,7 @@ s.configMu.RUnlock() // Detect interval changes and reset timers for affected sources - s.detectAndResetChangedIntervals(grouped) + s.detectAndResetChangedIntervals(grouped, oldSources) for tenantID, sourceMap := range grouped { integrations := make(map[string]Integration, len(sourceMap)) @@ -1470,11 +1476,10 @@ // detectAndResetChangedIntervals compares old and new source configs, // resetting discovery timers for sources whose intervals have changed. -func (s *SimpleSyncService) detectAndResetChangedIntervals(newSources map[string]map[string]*models.SourceConfig) { - s.tenantMu.RLock() - oldSources := s.tenantSources - s.tenantMu.RUnlock() - +func (s *SimpleSyncService) detectAndResetChangedIntervals( + newSources map[string]map[string]*models.SourceConfig, + oldSources map[string]map[string]*models.SourceConfig, +) { s.configMu.RLock() globalInterval := s.config.GetEffectiveDiscoveryInterval(nil) s.configMu.RUnlock() @@ -1589,6 +1594,22 @@ return snapshot } +func (s *SimpleSyncService) snapshotLegacySources() map[string]Integration { + s.configMu.RLock() + defer s.configMu.RUnlock() + + if len(s.sources) == 0 { + return nil + } + + snapshot := make(map[string]Integration, len(s.sources)) + for name, integration := range s.sources { + snapshot[name] = integration + } + + return snapshot +} ``` </details> ___ **To prevent a race condition in <code>runDiscovery</code>, create a snapshot of <code>s.sources</code> <br>under a read lock before using it.** [pkg/sync/service.go [407-419]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4ea5a5b3d811fa94c6a84c71c0ddb920ce177a376d13cb776f05dd6017c4c7eR407-R419) ```diff func (s *SimpleSyncService) runDiscovery(ctx context.Context) error { start := time.Now() s.logger.Info(). Time("started_at", start). Msg("Starting discovery cycle") tenantIntegrations := s.snapshotTenantIntegrations() if len(tenantIntegrations) == 0 { - allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", s.sources) + sourcesSnapshot := s.snapshotLegacySources() + allDeviceUpdates, discoveryErrors := s.runDiscoveryForIntegrations(ctx, "", "", sourcesSnapshot) s.updateResultsStore(allDeviceUpdates) if err := s.pushResultsForTenant(ctx, "", "", allDeviceUpdates); err != nil { s.logger.Error().Err(err).Msg("Failed to push sync results to gateway") } ... ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical race condition on the `s.sources` map, which could cause the application to panic, and proposes a valid solution. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>✅ <s>Fix race condition on tenant sources</s></summary> ___ <details><summary><b>Suggestion Impact:</b></summary>The commit changed concurrency handling around tenant sources by taking a tenantMu read-lock in setTenantSources to snapshot the previous s.tenantSources ("oldSources") and passing that snapshot into detectAndResetChangedIntervals, removing the internal lock from detectAndResetChangedIntervals. This relates to the suggested race-condition concern, but it does not implement the suggested fix of holding a tenantMu write lock for the full duration of setTenantSources modifications. code diff: ```diff @@ -1428,6 +1430,10 @@ tenantIntegrations := make(map[string]map[string]Integration, len(grouped)) tenantResults := make(map[string]*StreamingResultsStore, len(grouped)) + s.tenantMu.RLock() + oldSources := s.tenantSources + s.tenantMu.RUnlock() + // Read fallback values under lock s.configMu.RLock() agentID := s.config.AgentID @@ -1435,7 +1441,7 @@ s.configMu.RUnlock() // Detect interval changes and reset timers for affected sources - s.detectAndResetChangedIntervals(grouped) + s.detectAndResetChangedIntervals(grouped, oldSources) for tenantID, sourceMap := range grouped { integrations := make(map[string]Integration, len(sourceMap)) @@ -1470,11 +1476,10 @@ // detectAndResetChangedIntervals compares old and new source configs, // resetting discovery timers for sources whose intervals have changed. -func (s *SimpleSyncService) detectAndResetChangedIntervals(newSources map[string]map[string]*models.SourceConfig) { - s.tenantMu.RLock() - oldSources := s.tenantSources - s.tenantMu.RUnlock() - +func (s *SimpleSyncService) detectAndResetChangedIntervals( + newSources map[string]map[string]*models.SourceConfig, + oldSources map[string]map[string]*models.SourceConfig, +) { ``` </details> ___ **Fix a race condition in <code>setTenantSources</code> by acquiring a write lock on <code>s.tenantMu</code> <br>at the start of the function and holding it until all modifications are <br>complete.** [pkg/sync/service.go [1426-1441]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4ea5a5b3d811fa94c6a84c71c0ddb920ce177a376d13cb776f05dd6017c4c7eR1426-R1441) ```diff func (s *SimpleSyncService) setTenantSources(sources map[string]*models.SourceConfig, scope string) { + s.tenantMu.Lock() + defer s.tenantMu.Unlock() + grouped, slugs := s.groupSourcesByTenant(sources, scope) tenantIntegrations := make(map[string]map[string]Integration, len(grouped)) tenantResults := make(map[string]*StreamingResultsStore, len(grouped)) // Read fallback values under lock s.configMu.RLock() agentID := s.config.AgentID gatewayID := s.config.GatewayID s.configMu.RUnlock() // Detect interval changes and reset timers for affected sources s.detectAndResetChangedIntervals(grouped) for tenantID, sourceMap := range grouped { ... ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical race condition where `s.tenantSources` is read and then written without a continuous lock, which could lead to data corruption or panics. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>✅ <s>Schedule daily cleanup tick</s></summary> ___ <details><summary><b>Suggestion Impact:</b></summary>Implemented the missing cleanupTicker case in the select loop, invoking cleanupUnknownGateways on each tick and logging failures. code diff: ```diff + case <-cleanupTicker.C: + if err := s.cleanupUnknownGateways(ctx); err != nil { + s.logger.Error(). + Err(err). + Msg("Daily cleanup of unknown gateways failed") + } ``` </details> ___ **Add a case for <code>cleanupTicker</code> in the <code>select</code> loop to ensure the daily cleanup of <br>unknown gateways is executed as intended.** [pkg/core/gateways.go [44-73]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R44-R73) ```diff func (s *Server) MonitorGateways(ctx context.Context) { ticker := time.NewTicker(monitorInterval) defer ticker.Stop() cleanupTicker := time.NewTicker(dailyCleanupInterval) defer cleanupTicker.Stop() if err := s.checkGatewayStatus(ctx); err != nil { s.logger.Error(). Err(err). Msg("Initial state check failed") } for { select { case <-ctx.Done(): return case <-s.ShutdownChan: return case <-ticker.C: s.handleMonitorTick(ctx) + case <-cleanupTicker.C: + if err := s.cleanupUnknownGateways(ctx); err != nil { + s.logger.Error(). + Err(err). + Msg("Daily cleanup of unknown gateways failed") + } } } } ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a significant bug where the `cleanupTicker` is initialized but never used, causing a critical daily cleanup task to be skipped. This fix is essential for the intended functionality. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Prevent potential goroutine leak on cancellation</summary> ___ **Replace <code>context.WithoutCancel(ctx)</code> with the original <code>ctx</code> in the background <br>goroutine to prevent potential goroutine leaks if the request is cancelled.** [pkg/core/gateways.go [619-620]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R619-R620) ```diff // Run in a separate goroutine to not block the main status report flow. -// Create a detached context but preserve trace information -detachedCtx := context.WithoutCancel(ctx) -s.registerServiceOrCoreDevice(detachedCtx, req.GatewayId, req.Partition, normalizedIP, services, now) +s.registerServiceOrCoreDevice(ctx, req.GatewayId, req.Partition, normalizedIP, services, now) ``` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential goroutine leak due to using a detached context. While the original code's intent was to ensure registration completes, respecting context cancellation is a more robust and safer pattern to prevent resource exhaustion. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Persist gateway recovery status to DB</summary> ___ **Persist the gateway's recovered status to the database by queueing a status <br>update, ensuring the state is not lost on restart.** [pkg/core/gateways.go [148-149]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R148-R149) ```diff -// Simply clear the alert flag and mark as healthy - PushStatus handles proper recovery events +// Queue the status update to persist the recovery +s.queueGatewayStatusUpdate(ps.GatewayID, true, ps.LastSeen, ps.FirstSeen) + +// Also update the in-memory cache state ps.AlertSent = false ps.IsHealthy = true ``` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly identifies that the gateway recovery status is only updated in-memory and not persisted, which is a bug in the backup recovery mechanism. Applying this fix ensures state consistency with the database. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Refactor to prevent potential deadlocks</summary> ___ **Refactor <code>runDiscoveryForIntegrations</code> to avoid potential deadlocks by acquiring <br>all necessary locks at once, retrieving data, and then releasing them.** [pkg/sync/service.go [483-519]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-b4ea5a5b3d811fa94c6a84c71c0ddb920ce177a376d13cb776f05dd6017c4c7eR483-R519) ```diff func (s *SimpleSyncService) runDiscoveryForIntegrations( ctx context.Context, tenantID string, tenantSlug string, integrations map[string]Integration, ) (map[string][]*models.DeviceUpdate, []error) { ... for sourceName, integration := range integrations { - // Get source config to check per-source interval - sourceConfig := s.getSourceConfig(tenantID, sourceName) + sourceConfig, isDue, interval := s.checkIfSourceIsDue(tenantID, sourceName) - // Check if this source is due for discovery based on its interval - if !s.isSourceDueForDiscovery(tenantID, sourceName, sourceConfig) { -... + if !isDue { + s.logger.Debug(). + Str("source", sourceName). + Str("tenant_id", tenantID). + Dur("interval", interval). + Msg("Skipping discovery for source - not due yet") + continue } logEvent := s.logger.Info().Str("source", sourceName) -... - s.configMu.RLock() - interval := s.config.GetEffectiveDiscoveryInterval(sourceConfig) - s.configMu.RUnlock() + if tenantID != "" { + logEvent = logEvent.Str("tenant_id", tenantID) + } logEvent = logEvent.Dur("interval", interval) logEvent.Msg("Running discovery for source") ... ``` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies a potential deadlock scenario due to inconsistent lock ordering, and the proposed refactoring improves code safety and maintainability. </details></details></td><td align=center>Low </td></tr><tr><td> <details><summary>Distinguish not-found from query errors</summary> ___ **Refine error handling to specifically check for a "not found" error <br>(<code>db.ErrNotFound</code>) instead of a generic query failure when determining if a <br>gateway is new.** [pkg/core/gateways.go [445-454]](https://github.com/carverauto/serviceradar/pull/2233/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R445-R454) ```diff existingStatus, err := s.DB.GetGatewayStatus(ctx, gatewayID) -if err != nil && !errors.Is(err, db.ErrFailedToQuery) { - return fmt.Errorf("failed to check gateway existence: %w", err) -} - if err != nil { - gatewayStatus.FirstSeen = timestamp + if errors.Is(err, db.ErrNotFound) { + gatewayStatus.FirstSeen = timestamp + } else { + return fmt.Errorf("failed to check gateway existence: %w", err) + } } else { gatewayStatus.FirstSeen = existingStatus.FirstSeen } ``` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly points out that the error handling is too broad, treating all query failures as a "not found" case. Using a specific `db.ErrNotFound` check improves robustness by distinguishing between a missing record and an actual database error. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table> </details>
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!2639
No description provided.