Updates/log events work #2643

Merged
mfreeman451 merged 6 commits from refs/pull/2643/head into testing 2026-01-10 05:31:26 +00:00
mfreeman451 commented 2026-01-10 03:54:03 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2239
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2239
Original created: 2026-01-10T03:54:03Z
Original updated: 2026-01-10T05:31:28Z
Original head: carverauto/serviceradar:updates/log-events-work
Original base: testing
Original merged: 2026-01-10T05:31:26Z 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 terminology migration: Comprehensive rename of poller to gateway throughout the codebase (100+ files affected)

  • New gateway monitoring system: Implements comprehensive gateway health checks, offline/recovery detection, and event publishing to NATS with streaming status report handling

  • Agent-gateway communication: Adds PushLoop implementation for periodic agent status pushes with enrollment flow and config polling

  • NATS bootstrap CLI: New CLI command implementation for NATS operator/account generation with JWT and credentials file management

  • NATS account service: New test suite with 474 lines covering tenant account creation, user credential generation, and JWT signing

  • Database updates: Renamed poller_id columns to gateway_id across metrics and discovery layers

  • API refactoring: Updated REST API routes from /pollers/{id} to /gateways/{id} with corresponding type and method renames

  • Edge onboarding updates: Added EdgeOnboardingComponentTypeSync support and updated KV storage paths from config/pollers/ to config/gateways/

  • Deleted legacy components: Removed poller-specific files including cmd/poller/main.go, cmd/sync/main.go, and related Docker/Helm configurations


Diagram Walkthrough

flowchart LR
  Agent["Agent"] -->|"Hello + GetConfig"| Gateway["Gateway<br/>Monitoring System"]
  Gateway -->|"Status Events"| NATS["NATS<br/>Event Bus"]
  CLI["NATS Bootstrap<br/>CLI"] -->|"Generate JWT"| Operator["NATS Operator<br/>& Accounts"]
  Gateway -->|"Registry Integration"| Registry["Service Registry<br/>Auto-registration"]
  Agent -->|"Push Status"| Gateway
  NATS -->|"Stream Reports"| DataSvc["Data Services<br/>Account Management"]

File Walkthrough

Relevant files
Enhancement
3 files
gateways.go
Gateway monitoring and status management implementation   

pkg/core/gateways.go

  • New comprehensive gateway monitoring and status management
    implementation with 1549 lines of core functionality
  • Implements gateway health checks, offline/recovery detection, and
    event publishing to NATS
  • Adds streaming status report handling with chunk reassembly for large
    datasets
  • Includes service registry integration for auto-registration of
    gateways, agents, and checkers
+1549/-0
nats_bootstrap.go
Add NATS bootstrap CLI command implementation                       

pkg/cli/nats_bootstrap.go

  • New file implementing NATS bootstrap CLI functionality with 961 lines
    of code
  • Provides handlers for nats-bootstrap, admin nats
    generate-bootstrap-token, admin nats status, and admin nats tenants
    subcommands
  • Implements local and remote NATS operator/account generation with JWT
    and credentials file management
  • Includes configuration file generation, verification, and output
    formatting (text/JSON)
+961/-0 
push_loop.go
Add push loop implementation for agent-gateway communication

pkg/agent/push_loop.go

  • New file implementing PushLoop struct for managing periodic agent
    status pushes to gateway
  • Implements enrollment flow with gateway via Hello and GetConfig RPC
    calls
  • Provides config polling loop to fetch and apply checker configurations
    from gateway
  • Includes status collection from services and checkers with conversion
    to GatewayServiceStatus proto messages
  • Implements network interface enumeration to determine source IP for
    status reports
+979/-0 
Refactoring
8 files
edge_onboarding.go
Poller to gateway terminology refactoring and sync support

pkg/core/edge_onboarding.go

  • Systematic rename of poller terminology to gateway throughout the file
    (100+ occurrences)
  • Updates function names, variable names, struct fields, and comments to
    reflect gateway naming convention
  • Adds support for EdgeOnboardingComponentTypeSync component type in
    package creation and validation
  • Updates KV storage paths from config/pollers/ to config/gateways/ for
    consistency
+208/-206
cnpg_discovery.go
Database column and variable naming updates for gateway   

pkg/db/cnpg_discovery.go

  • Rename of poller_id column references to gateway_id in SQL queries and
    function arguments
  • Updates variable names from pollerID to gatewayID in discovery
    interface and topology event builders
  • Maintains consistency with gateway terminology across database
    operations
+8/-8     
edge_onboarding_test.go
Rename poller to gateway in edge onboarding tests               

pkg/core/edge_onboarding_test.go

  • Renamed all references from poller to gateway terminology throughout
    test cases
  • Updated function names: validPollerMetadataJSON()
    validGatewayMetadataJSON()
  • Updated mock expectations: ListEdgeOnboardingPollerIDs()
    ListEdgeOnboardingGatewayIDs()
  • Updated callback methods: SetAllowedPollerCallback()
    SetAllowedGatewayCallback()
  • Updated model field references: PollerIDGatewayID,
    ComponentTypePollerComponentTypeGateway
+106/-106
metrics.go
Rename poller to gateway in metrics processing                     

pkg/core/metrics.go

  • Renamed parameter pollerID to gatewayID across all metric processing
    functions
  • Updated function signatures: createSNMPMetric(), bufferMetrics(),
    processSNMPMetrics(), processRperfMetrics(), processSysmonMetrics(),
    processICMPMetrics(), processGRPCService(), processServicePayload()
  • Updated model field assignments: PollerIDGatewayID in metric
    structures
  • Updated logging statements to use gateway_id instead of poller_id
  • Updated metadata keys and event metadata to reflect gateway
    terminology
+122/-122
cnpg_metrics.go
Rename poller to gateway in database metrics layer             

pkg/db/cnpg_metrics.go

  • Updated SQL column references from poller_id to gateway_id in INSERT
    statements for all metric tables
  • Renamed function parameters from pollerID to gatewayID in all metric
    insertion functions
  • Updated logging statements to use gateway_id instead of poller_id
  • Updated argument builder functions to pass gatewayID instead of
    pollerID
+33/-33 
server_test.go
Rename poller terminology to gateway throughout server tests

pkg/core/server_test.go

  • Renamed all references from poller to gateway throughout test cases
    and mock setups
  • Updated field names: MaxPollersMaxGateways, PollerStatus
    GatewayStatus, pollerStatusCachegatewayStatusCache
  • Changed test function names: TestReportStatusTestPushStatus,
    TestUpdatePollerStatusTestUpdateGatewayStatus
  • Updated proto message types: PollerStatusRequest
    GatewayStatusRequest, ServiceStatusGatewayServiceStatus
  • Fixed alignment/formatting of struct field assignments for consistency
+177/-177
server.go
Refactor API server to use gateway terminology instead of poller

pkg/core/api/server.go

  • Renamed map fields: pollersgateways, knownPollerSet
    knownGatewaySet, dynamicPollersdynamicGateways
  • Updated method names: getPollersgetGateways, getPoller
    getGateway, UpdatePollerStatusUpdateGatewayStatus
  • Changed API route paths from /pollers/{id} to /gateways/{id} and
    related endpoints
  • Updated struct types: PollerNodeGatewayNode, PollerStatus
    GatewayStatus, PollerHistoryPointGatewayHistoryPoint
  • Updated security role: models.RolePollermodels.RoleGateway
  • Changed field names in responses: TotalPollersTotalGateways,
    HealthyPollersHealthyGateways
+161/-161
mock_db.go
Update mock database methods to use gateway terminology   

pkg/db/mock_db.go

  • Renamed mock methods: DeletePollerDeleteGateway, GetPollerStatus
    GetGatewayStatus, UpdatePollerStatusUpdateGatewayStatus
  • Updated return types: PollerStatusGatewayStatus, PollerHistoryPoint
    GatewayHistoryPoint
  • Changed method names: GetPollerHistoryGetGatewayHistory,
    GetPollerServicesGetGatewayServices, ListPollersListGateways
  • Updated list methods: ListPollerStatusesListGatewayStatuses,
    ListAgentsByPollerListAgentsByGateway
  • Renamed offline check: IsPollerOfflineIsGatewayOffline
+82/-82 
Tests
1 files
nats_account_service_test.go
NATS account service test suite implementation                     

pkg/datasvc/nats_account_service_test.go

  • New comprehensive test suite for NATS account service with 474 lines
    of test coverage
  • Tests for tenant account creation, user credential generation, and
    account JWT signing
  • Includes authorization context testing and proto conversion utility
    tests
  • Covers success cases, error handling, and edge cases with proper
    assertions
+474/-0 
Documentation
1 files
main.go
Update API documentation description                                         

cmd/core/main.go

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

elixir/serviceradar_core_elx/config/prod.exs

  • New production configuration file for Elixir application
  • Sets logger level to info for production environment
+3/-0     
Additional files
101 files
.bazelignore +4/-0     
.bazelrc +5/-0     
.env-sample +33/-0   
.env.example +38/-0   
main.yml +18/-0   
AGENTS.md +177/-11
INSTALL.md +11/-11 
MODULE.bazel +5/-0     
Makefile +55/-14 
README-Docker.md +17/-2   
README.md +3/-3     
ROADMAP.md +1/-1     
BUILD.bazel +11/-6   
BUILD.bazel +12/-0   
mix_release.bzl +124/-49
BUILD.bazel +1/-0     
README.md +4/-4     
config.json +5/-6     
main.go +174/-74
build.rs +0/-1     
monitoring.proto +3/-26   
server.rs +2/-0     
main.go +1/-1     
README.md +2/-2     
monitoring.proto +2/-26   
server.rs +6/-6     
main.go +16/-2   
README.md +8/-8     
config.rs +38/-12 
grpc_server.rs +2/-2     
message_processor.rs +1/-0     
nats.rs +4/-0     
zen-consumer-with-otel.json +9/-8     
zen-consumer.json +9/-8     
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 +2/-1     
nats_output.rs +14/-0   
otel.toml +3/-1     
otel.toml.example +5/-2     
config.rs +21/-3   
nats_output.rs +22/-5   
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     
README.md +3/-3     
config.rs +22/-1   
main.rs +23/-3   
docker-compose.elx.yml +117/-0 
docker-compose.spiffe.yml +8/-158 
docker-compose.yml +326/-269
Dockerfile.agent-gateway +94/-0   
Dockerfile.core-elx +108/-0 
Dockerfile.poller +0/-70   
Dockerfile.sync +0/-95   
Dockerfile.tools +1/-2     
Dockerfile.web-ng +6/-0     
agent-minimal.docker.json +6/-6     
agent.docker.json +5/-20   
agent.mtls.json +7/-10   
bootstrap-nested-spire.sh +0/-80   
.gitkeep +1/-0     
datasvc.docker.json +3/-2     
datasvc.mtls.json +14/-1   
db-event-writer.docker.json +10/-10 
db-event-writer.mtls.json +9/-8     
FRICTION_POINTS.md +0/-355 
README.md +0/-207 
SETUP_GUIDE.md +0/-307 
docker-compose.edge-e2e.yml +0/-27   
manage-packages.sh +0/-211 
setup-edge-e2e.sh +0/-198 
edge-poller-restart.sh +0/-178 
downstream-agent.conf +0/-32   
env +0/-4     
server.conf +0/-51   
upstream-agent.conf +0/-32   
entrypoint-certs.sh +13/-9   
entrypoint-poller.sh +0/-274 
entrypoint-sync.sh +0/-96   
fix-cert-permissions.sh +2/-2     
flowgger.docker.toml +3/-2     
generate-certs.sh +214/-12
nats.docker.conf +16/-160
netflow-consumer.mtls.json +1/-0     
otel.docker.toml +7/-2     
pg_hba.conf +9/-0     
pg_ident.conf +17/-0   
poller-stack.compose.yml +0/-121 
poller.docker.json +0/-128 
Additional files not shown

Imported from GitHub pull request. Original GitHub pull request: #2239 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2239 Original created: 2026-01-10T03:54:03Z Original updated: 2026-01-10T05:31:28Z Original head: carverauto/serviceradar:updates/log-events-work Original base: testing Original merged: 2026-01-10T05:31:26Z 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 terminology migration**: Comprehensive rename of `poller` to `gateway` throughout the codebase (100+ files affected) - **New gateway monitoring system**: Implements comprehensive gateway health checks, offline/recovery detection, and event publishing to NATS with streaming status report handling - **Agent-gateway communication**: Adds `PushLoop` implementation for periodic agent status pushes with enrollment flow and config polling - **NATS bootstrap CLI**: New CLI command implementation for NATS operator/account generation with JWT and credentials file management - **NATS account service**: New test suite with 474 lines covering tenant account creation, user credential generation, and JWT signing - **Database updates**: Renamed `poller_id` columns to `gateway_id` across metrics and discovery layers - **API refactoring**: Updated REST API routes from `/pollers/{id}` to `/gateways/{id}` with corresponding type and method renames - **Edge onboarding updates**: Added `EdgeOnboardingComponentTypeSync` support and updated KV storage paths from `config/pollers/` to `config/gateways/` - **Deleted legacy components**: Removed poller-specific files including `cmd/poller/main.go`, `cmd/sync/main.go`, and related Docker/Helm configurations ___ ### Diagram Walkthrough ```mermaid flowchart LR Agent["Agent"] -->|"Hello + GetConfig"| Gateway["Gateway<br/>Monitoring System"] Gateway -->|"Status Events"| NATS["NATS<br/>Event Bus"] CLI["NATS Bootstrap<br/>CLI"] -->|"Generate JWT"| Operator["NATS Operator<br/>& Accounts"] Gateway -->|"Registry Integration"| Registry["Service Registry<br/>Auto-registration"] Agent -->|"Push Status"| Gateway NATS -->|"Stream Reports"| DataSvc["Data Services<br/>Account Management"] ``` <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>3 files</summary><table> <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 1549 lines of core functionality<br> <li> Implements gateway health checks, offline/recovery detection, and <br>event publishing to NATS<br> <li> Adds streaming status report handling with chunk reassembly for large <br>datasets<br> <li> Includes service registry integration for auto-registration of <br>gateways, agents, and checkers</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0">+1549/-0</a></td> </tr> <tr> <td> <details> <summary><strong>nats_bootstrap.go</strong><dd><code>Add NATS bootstrap CLI command implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/cli/nats_bootstrap.go <ul><li>New file implementing NATS bootstrap CLI functionality with 961 lines <br>of code<br> <li> Provides handlers for <code>nats-bootstrap</code>, <code>admin nats </code><br><code>generate-bootstrap-token</code>, <code>admin nats status</code>, and <code>admin nats tenants</code> <br>subcommands<br> <li> Implements local and remote NATS operator/account generation with JWT <br>and credentials file management<br> <li> Includes configuration file generation, verification, and output <br>formatting (text/JSON)</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457">+961/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>push_loop.go</strong><dd><code>Add push loop implementation for agent-gateway communication</code></dd></summary> <hr> pkg/agent/push_loop.go <ul><li>New file implementing <code>PushLoop</code> struct for managing periodic agent <br>status pushes to gateway<br> <li> Implements enrollment flow with gateway via <code>Hello</code> and <code>GetConfig</code> RPC <br>calls<br> <li> Provides config polling loop to fetch and apply checker configurations <br>from gateway<br> <li> Includes status collection from services and checkers with conversion <br>to <code>GatewayServiceStatus</code> proto messages<br> <li> Implements network interface enumeration to determine source IP for <br>status reports</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785">+979/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Refactoring</strong></td><td><details><summary>8 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>Systematic rename of <code>poller</code> terminology to <code>gateway</code> throughout the file <br>(100+ occurrences)<br> <li> Updates function names, variable names, struct fields, and comments to <br>reflect gateway naming convention<br> <li> Adds support for <code>EdgeOnboardingComponentTypeSync</code> component type in <br>package creation and validation<br> <li> Updates KV storage paths from <code>config/pollers/</code> to <code>config/gateways/</code> for <br>consistency</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-85874e3c4bdcc9110db09909f10648d44cdee554b26c987f910502321eb20b5c">+208/-206</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_discovery.go</strong><dd><code>Database column and variable naming updates for gateway</code>&nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_discovery.go <ul><li>Rename of <code>poller_id</code> column references to <code>gateway_id</code> in SQL queries and <br>function arguments<br> <li> Updates variable names from <code>pollerID</code> to <code>gatewayID</code> in discovery <br>interface and topology event builders<br> <li> Maintains consistency with gateway terminology across database <br>operations</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-f26ec5cc4a99562923f5b5a1470bfb5f2ec6296d7e1270ab8b924040980532bf">+8/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>edge_onboarding_test.go</strong><dd><code>Rename poller to gateway in edge onboarding tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/core/edge_onboarding_test.go <ul><li>Renamed all references from <code>poller</code> to <code>gateway</code> terminology throughout <br>test cases<br> <li> Updated function names: <code>validPollerMetadataJSON()</code> → <br><code>validGatewayMetadataJSON()</code><br> <li> Updated mock expectations: <code>ListEdgeOnboardingPollerIDs()</code> → <br><code>ListEdgeOnboardingGatewayIDs()</code><br> <li> Updated callback methods: <code>SetAllowedPollerCallback()</code> → <br><code>SetAllowedGatewayCallback()</code><br> <li> Updated model field references: <code>PollerID</code> → <code>GatewayID</code>, <br><code>ComponentTypePoller</code> → <code>ComponentTypeGateway</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-83ba269452783436d693c0f17ab42c959efdef44c7cf6ec27301dd1b0e7d5744">+106/-106</a></td> </tr> <tr> <td> <details> <summary><strong>metrics.go</strong><dd><code>Rename poller to gateway in metrics processing</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/core/metrics.go <ul><li>Renamed parameter <code>pollerID</code> to <code>gatewayID</code> across all metric processing <br>functions<br> <li> Updated function signatures: <code>createSNMPMetric()</code>, <code>bufferMetrics()</code>, <br><code>processSNMPMetrics()</code>, <code>processRperfMetrics()</code>, <code>processSysmonMetrics()</code>, <br><code>processICMPMetrics()</code>, <code>processGRPCService()</code>, <code>processServicePayload()</code><br> <li> Updated model field assignments: <code>PollerID</code> → <code>GatewayID</code> in metric <br>structures<br> <li> Updated logging statements to use <code>gateway_id</code> instead of <code>poller_id</code><br> <li> Updated metadata keys and event metadata to reflect gateway <br>terminology</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-6d98e853ce17576c088e77956ae4ecfa8078019e0bff107a79d8d1d6ed2443ad">+122/-122</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_metrics.go</strong><dd><code>Rename poller to gateway in database metrics layer</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_metrics.go <ul><li>Updated SQL column references from <code>poller_id</code> to <code>gateway_id</code> in INSERT <br>statements for all metric tables<br> <li> Renamed function parameters from <code>pollerID</code> to <code>gatewayID</code> in all metric <br>insertion functions<br> <li> Updated logging statements to use <code>gateway_id</code> instead of <code>poller_id</code><br> <li> Updated argument builder functions to pass <code>gatewayID</code> instead of <br><code>pollerID</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-8ca23b1b2b298b6eec9af9e23be2c7cb9caa65301f514c013ebbc5b52e933a23">+33/-33</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server_test.go</strong><dd><code>Rename poller terminology to gateway throughout server tests</code></dd></summary> <hr> pkg/core/server_test.go <ul><li>Renamed all references from <code>poller</code> to <code>gateway</code> throughout test cases <br>and mock setups<br> <li> Updated field names: <code>MaxPollers</code> → <code>MaxGateways</code>, <code>PollerStatus</code> → <br><code>GatewayStatus</code>, <code>pollerStatusCache</code> → <code>gatewayStatusCache</code><br> <li> Changed test function names: <code>TestReportStatus</code> → <code>TestPushStatus</code>, <br><code>TestUpdatePollerStatus</code> → <code>TestUpdateGatewayStatus</code><br> <li> Updated proto message types: <code>PollerStatusRequest</code> → <br><code>GatewayStatusRequest</code>, <code>ServiceStatus</code> → <code>GatewayServiceStatus</code><br> <li> Fixed alignment/formatting of struct field assignments for consistency</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-a406eabc08b2c0cc98ca61d603c8dceb8e4592c989be63da2735d705d20db86a">+177/-177</a></td> </tr> <tr> <td> <details> <summary><strong>server.go</strong><dd><code>Refactor API server to use gateway terminology instead of poller</code></dd></summary> <hr> pkg/core/api/server.go <ul><li>Renamed map fields: <code>pollers</code> → <code>gateways</code>, <code>knownPollerSet</code> → <br><code>knownGatewaySet</code>, <code>dynamicPollers</code> → <code>dynamicGateways</code><br> <li> Updated method names: <code>getPollers</code> → <code>getGateways</code>, <code>getPoller</code> → <br><code>getGateway</code>, <code>UpdatePollerStatus</code> → <code>UpdateGatewayStatus</code><br> <li> Changed API route paths from <code>/pollers/{id}</code> to <code>/gateways/{id}</code> and <br>related endpoints<br> <li> Updated struct types: <code>PollerNode</code> → <code>GatewayNode</code>, <code>PollerStatus</code> → <br><code>GatewayStatus</code>, <code>PollerHistoryPoint</code> → <code>GatewayHistoryPoint</code><br> <li> Updated security role: <code>models.RolePoller</code> → <code>models.RoleGateway</code><br> <li> Changed field names in responses: <code>TotalPollers</code> → <code>TotalGateways</code>, <br><code>HealthyPollers</code> → <code>HealthyGateways</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-1bb99367fdd853c728b7cfbf5893a293f6d217144dfb5282cb8dd32e5261021e">+161/-161</a></td> </tr> <tr> <td> <details> <summary><strong>mock_db.go</strong><dd><code>Update mock database methods to use gateway terminology</code>&nbsp; &nbsp; </dd></summary> <hr> pkg/db/mock_db.go <ul><li>Renamed mock methods: <code>DeletePoller</code> → <code>DeleteGateway</code>, <code>GetPollerStatus</code> → <br><code>GetGatewayStatus</code>, <code>UpdatePollerStatus</code> → <code>UpdateGatewayStatus</code><br> <li> Updated return types: <code>PollerStatus</code> → <code>GatewayStatus</code>, <code>PollerHistoryPoint</code> <br>→ <code>GatewayHistoryPoint</code><br> <li> Changed method names: <code>GetPollerHistory</code> → <code>GetGatewayHistory</code>, <br><code>GetPollerServices</code> → <code>GetGatewayServices</code>, <code>ListPollers</code> → <code>ListGateways</code><br> <li> Updated list methods: <code>ListPollerStatuses</code> → <code>ListGatewayStatuses</code>, <br><code>ListAgentsByPoller</code> → <code>ListAgentsByGateway</code><br> <li> Renamed offline check: <code>IsPollerOffline</code> → <code>IsGatewayOffline</code></ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-30e38f888d4849fc40d7ebb1559c2a84c43aa8cd13b3b89fd7ec6cf873b243c7">+82/-82</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>nats_account_service_test.go</strong><dd><code>NATS account service test suite implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/datasvc/nats_account_service_test.go <ul><li>New comprehensive test suite for NATS account service with 474 lines <br>of test coverage<br> <li> Tests for tenant account creation, user credential generation, and <br>account JWT signing<br> <li> Includes authorization context testing and proto conversion utility <br>tests<br> <li> Covers success cases, error handling, and edge cases with proper <br>assertions</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-3d1ee22d8514e26752c45da92cfacb867a80d29cb8bfc3682b5784d123bcfc52">+474/-0</a>&nbsp; </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 description</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/core/main.go <ul><li>Updated API description from "service pollers" to "service gateways"</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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>1 files</summary><table> <tr> <td> <details> <summary><strong>prod.exs</strong><dd><code>Add Elixir production configuration file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> elixir/serviceradar_core_elx/config/prod.exs <ul><li>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/2239/files#diff-f5d592c162d5ecb76b603fa735e9df064de660a0957dc8c489883ef74bd05291">+3/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>101 files</summary><table> <tr> <td><strong>.bazelignore</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-a54ff182c7e8acf56acfd6e4b9c3ff41e2c41a31c9b211b2deb9df75d9a478f9">+177/-11</a></td> </tr> <tr> <td><strong>INSTALL.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-86ec281f99363b6b6eb1f49e21d83b7eeca93a35b552b9f305fffc6855e38ccd">+124/-49</a></td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/files#diff-61358711e980ccf505246fd3915f97cbd3a380e9b66f6fa5aad46749968c5ca3">+174/-74</a></td> </tr> <tr> <td><strong>build.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/files#diff-bce0f4ca6548712f224b73816825d28e831acbbff7dbed3c98671ed50f65d028">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-f25402eade63525184cb5e7437accff93c7b9338eebe81add6dc5f2a9eb12550">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/files#diff-ed4d81d29a7267f93fd77e17993fd3491b9ef6ded18490b4514d10ed1d803bc2">+16/-2</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-643d2c3959322902c5bc9a22666b1e9ef71fa0bb87c9451b0e4147a4d5b51987">+8/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-05038f3867985e757de9027609950e682bad6d1992dac6acd7c28962a3c65dc4">+38/-12</a>&nbsp; </td> </tr> <tr> <td><strong>grpc_server.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/files#diff-68375f1f7847e1fbdf75664f6be65b1ad94ae6ce86ed73fc5964d65054668acb">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>zen-consumer.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-4d308af9802a93a0f656e8c02a3b5fcd8991407bb18360f087470db74e1f9524">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>app.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-af9f49f931e282dca53d1f0521b036d222fe671f77e61a876a84cf4c6d7cca4d">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>nats_output.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/files#diff-c64b9ace832b8ea57a2be62f84166e03bb1904882635d444ec76a880cdf14cc0">+3/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.toml.example</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-c1889866f35f98cdba9cd229fc119273c5fa5fca501451db23813b575f6fec66">+5/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-abbaec651da3d6af96b482e0f77bb909b65dbe0cabd78b5803769cc9dab0a1b0">+21/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>nats_output.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-6b585ea3564a481174e04da1270e2e13edd4e2b980d02a2652d6d21e6d82a498">+22/-5</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>setup.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-bc6eeb1b05bcb9179525e32fac1de9926b5823ec3504be546ab10c5c9740f544">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-9c32ee8446458b6fd2ae7fee52016f4b707a59978b67888cd5bee2804d934528">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>config.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-c89b88ba4d2bf0a054d0ba69a672a92c30140b8d19503d67b980a218ffe3106d">+22/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.rs</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/files#diff-9562070d7ad4a3e9b2d06567008cf35de1d96448d914b3b45bf6c36d97cdd914">+117/-0</a>&nbsp; </td> </tr> <tr> <td><strong>docker-compose.spiffe.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/files#diff-e45e45baeda1c1e73482975a664062aa56f20c03dd9d64a827aba57775bed0d3">+326/-269</a></td> </tr> <tr> <td><strong>Dockerfile.agent-gateway</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-ab4746a08fb1e0b307a1e47660cd22182e283a087cba87dcbff0fdfe750f44f1">+0/-80</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>.gitkeep</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-d72c41aab2d6f2c230a4340dfefe7917cdd12bed942c825aa0d4c9875a637bac">+1/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>datasvc.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/files#diff-9fc51271f7ef5bb460160013e24e44e829b730656891d26fc49d5fe72fbb3147">+10/-10</a>&nbsp; </td> </tr> <tr> <td><strong>db-event-writer.mtls.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-7a33f95f7545499abf0ed9fc91b58499ab209639e4885019579c959583fc7496">+9/-8</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>FRICTION_POINTS.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/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/2239/files#diff-824f8797b418d4b9f5ea41e4a3741a0ed64b881f343072464489a76b7ea01008">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>generate-certs.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/files#diff-d4af38790e3657b7589cd37a7539d5308b032f11caba7aa740ddc86bf99f4415">+7/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>pg_hba.conf</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/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/2239/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/2239/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/2239/files#diff-d64ebb69ec31e831efd187c47a5bfff2573960306b177f6464e91cb44a3c709d">+0/-128</a>&nbsp; </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-01-10 03:55:18 +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/2239#issuecomment-3731785021
Original created: 2026-01-10T03:55:18Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Sensitive seed disclosure

Description: The CLI exposes highly sensitive NATS secrets (operator/system seeds) by printing them to
stdout and including them in JSON output (e.g., output["operator_seed"],
output["system_account_seed"], and the text output that prints Operator Seed/System
Account Seed
), which can leak via terminal history, logs, CI artifacts, or process
capture.
nats_bootstrap.go [471-530]

Referred Code
func outputNATSBootstrapJSON(result *natsBootstrapAPIResponse, paths *natsBootstrapPaths) error {
	output := map[string]interface{}{
		"operator_public_key":            result.OperatorPublicKey,
		"system_account_public_key":      result.SystemAccountPublicKey,
		"config_path":                    paths.configPath,
		"operator_jwt_path":              paths.operatorJWTPath,
		"operator_seed_path":             paths.operatorSeedPath,
		"jwt_resolver_dir":               paths.jwtDir,
		"system_account_public_key_path": paths.systemAccountPublicKeyPath,
		"system_creds_path":              paths.systemCredsPath,
		"platform_creds_path":            paths.platformCredsPath,
		"platform_account_public_key":    paths.platformAccountPublicKey,
	}
	if result.OperatorSeed != "" {
		output["operator_seed"] = result.OperatorSeed
	}
	if result.SystemAccountSeed != "" {
		output["system_account_seed"] = result.SystemAccountSeed
	}
	data, err := json.MarshalIndent(output, "", "  ")
	if err != nil {



 ... (clipped 39 lines)
TLS verification bypass

Description: The --tls-skip-verify option is plumbed into newHTTPClient(cfg.TLSSkipVerify) for Core API
calls, enabling certificate verification bypass and creating a realistic MITM risk if
users run the command with this flag in non-controlled environments.
nats_bootstrap.go [52-213]

Referred Code
func (NatsBootstrapHandler) Parse(args []string, cfg *CmdConfig) error {
	fs := flag.NewFlagSet("nats-bootstrap", flag.ExitOnError)
	coreURL := fs.String("core-url", defaultCoreURL, "ServiceRadar core base URL")
	apiKey := fs.String("api-key", "", "API key for authenticating with core")
	bearer := fs.String("bearer", "", "Bearer token for authenticating with core")
	skipTLS := fs.Bool("tls-skip-verify", false, "Skip TLS certificate verification")
	token := fs.String("token", "", "Platform bootstrap token")
	outputDir := fs.String("output-dir", defaultNATSOutputDir, "Directory to write NATS configuration files")
	operatorName := fs.String("operator-name", defaultNATSOperatorName, "Name for the NATS operator")
	importSeed := fs.String("import-operator-seed", "", "Import existing operator seed instead of generating new")
	verify := fs.Bool("verify", false, "Verify existing NATS bootstrap configuration")
	configPath := fs.String("config", "", "Path to existing nats.conf for verification")
	local := fs.Bool("local", false, "Generate NATS operator and accounts locally without core API")
	noSystemAccount := fs.Bool("no-system-account", false, "Skip system account generation")
	jetstream := fs.Bool("jetstream", true, "Enable JetStream")
	jetstreamDir := fs.String("jetstream-dir", "/var/lib/nats/jetstream", "JetStream storage directory")
	tlsCert := fs.String("tls-cert", "", "Path to TLS certificate for NATS server")
	tlsKey := fs.String("tls-key", "", "Path to TLS private key for NATS server")
	tlsCA := fs.String("tls-ca", "", "Path to TLS CA certificate for client verification")
	noTLS := fs.Bool("no-tls", false, "Disable TLS for NATS server")
	writeSystemCreds := fs.Bool("write-system-creds", true, "Write system account creds when available")



 ... (clipped 141 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: Robust Error Handling and Edge Case Management

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

Status:
Silent config drops: Invalid/unsupported checker configs can be silently skipped (returning nil) without any
warning/error logging, making misconfigurations difficult to detect and debug.

Referred Code
// protoCheckToCheckerConfig converts a proto AgentCheckConfig to a CheckerConfig.
func protoCheckToCheckerConfig(check *proto.AgentCheckConfig) *CheckerConfig {
	if check == nil {
		return nil
	}

	target := normalizeCheckTarget(check.Target)
	port := normalizeCheckPort(check.Port)
	wantHTTPS := check.CheckType == "https"
	checkerType := mapCheckType(check.CheckType)
	if (checkerType == tcpCheckType || checkerType == grpcCheckType) && port == 0 {
		// Allow "host:port" or URL targets that already include a port.
		hasEmbeddedPort := false
		if host, p, err := net.SplitHostPort(target); err == nil && host != "" && p != "" {
			hasEmbeddedPort = true
		} else if parsed, err := url.Parse(target); err == nil && parsed.Host != "" && parsed.Port() != "" {
			hasEmbeddedPort = true
		} else {
			// Handles "host:port/path" inputs without an explicit scheme.
			hostPort := target
			if i := strings.IndexAny(hostPort, "/?"); i >= 0 {



 ... (clipped 12 lines)

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

Generic: Secure Logging Practices

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

Status:
Sensitive target logging: The code logs check.Target verbatim when applying gateway-provided checks, which could
include sensitive data (e.g., URLs with embedded credentials or internal endpoints) and
therefore risks leaking secrets into logs.

Referred Code
	p.logger.Info().
		Str("name", check.Name).
		Str("type", check.CheckType).
		Str("target", check.Target).
		Int32("port", check.Port).
		Msg("Added/updated check from gateway config")
}

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

Generic: Comprehensive Audit Trails

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

Status:
Missing audit context: The new enrollment/config-application and status-push flows add operational logs but the
diff does not demonstrate audit-grade logging that consistently includes an acting
identity and outcome for critical actions across the system.

Referred Code
// enroll sends Hello to the gateway and fetches initial config.
func (p *PushLoop) enroll(ctx context.Context) {
	p.logger.Info().Msg("Enrolling with gateway...")
	p.initSync(ctx)

	// Build Hello request
	p.server.mu.RLock()
	agentID := p.server.config.AgentID
	p.server.mu.RUnlock()
	helloReq := &proto.AgentHelloRequest{
		AgentId:       agentID,
		Version:       Version, // Agent version from version.go
		Capabilities:  getAgentCapabilities(),
		ConfigVersion: p.getConfigVersion(),
	}

	// Send Hello
	helloResp, err := p.gateway.Hello(ctx, helloReq)
	if err != nil {
		p.logger.Error().Err(err).Msg("Failed to enroll with gateway")
		return



 ... (clipped 141 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:
Gateway config trust: The agent consumes and applies gateway-provided check configuration (targets/paths/ports)
and while some validation exists, the diff does not show authentication/authorization and
provenance guarantees for this external input.

Referred Code
// fetchAndApplyConfig fetches config from gateway and applies it.
func (p *PushLoop) fetchAndApplyConfig(ctx context.Context) {
	p.server.mu.RLock()
	agentID := p.server.config.AgentID
	p.server.mu.RUnlock()
	configReq := &proto.AgentConfigRequest{
		AgentId:       agentID,
		ConfigVersion: p.getConfigVersion(),
	}

	configResp, err := p.gateway.GetConfig(ctx, configReq)
	if err != nil {
		p.logger.Error().Err(err).Msg("Failed to fetch config from gateway")
		return
	}

	// If config hasn't changed, nothing to do
	if configResp.NotModified {
		p.logger.Debug().Str("version", p.getConfigVersion()).Msg("Config not modified")
		return
	}



 ... (clipped 112 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/2239#issuecomment-3731785021 Original created: 2026-01-10T03:55:18Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/2e8a520e33ad80ae571f5808eee7208e30a79ed8 --> Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=1>🔴</td> <td><details><summary><strong>Sensitive seed disclosure </strong></summary><br> <b>Description:</b> The CLI exposes highly sensitive NATS secrets (operator/system seeds) by printing them to <br>stdout and including them in JSON output (e.g., <code>output["operator_seed"]</code>, <br><code>output["system_account_seed"]</code>, and the text output that prints <code>Operator Seed</code>/<code>System <br>Account Seed</code>), which can leak via terminal history, logs, CI artifacts, or process <br>capture.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2239/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457R471-R530'>nats_bootstrap.go [471-530]</a></strong><br> <details open><summary>Referred Code</summary> ```go func outputNATSBootstrapJSON(result *natsBootstrapAPIResponse, paths *natsBootstrapPaths) error { output := map[string]interface{}{ "operator_public_key": result.OperatorPublicKey, "system_account_public_key": result.SystemAccountPublicKey, "config_path": paths.configPath, "operator_jwt_path": paths.operatorJWTPath, "operator_seed_path": paths.operatorSeedPath, "jwt_resolver_dir": paths.jwtDir, "system_account_public_key_path": paths.systemAccountPublicKeyPath, "system_creds_path": paths.systemCredsPath, "platform_creds_path": paths.platformCredsPath, "platform_account_public_key": paths.platformAccountPublicKey, } if result.OperatorSeed != "" { output["operator_seed"] = result.OperatorSeed } if result.SystemAccountSeed != "" { output["system_account_seed"] = result.SystemAccountSeed } data, err := json.MarshalIndent(output, "", " ") if err != nil { ... (clipped 39 lines) ``` </details></details></td></tr> <tr><td rowspan=1>⚪</td> <td><details><summary><strong>TLS verification bypass </strong></summary><br> <b>Description:</b> The <code>--tls-skip-verify</code> option is plumbed into <code>newHTTPClient(cfg.TLSSkipVerify)</code> for Core API <br>calls, enabling certificate verification bypass and creating a realistic MITM risk if <br>users run the command with this flag in non-controlled environments.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2239/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457R52-R213'>nats_bootstrap.go [52-213]</a></strong><br> <details open><summary>Referred Code</summary> ```go func (NatsBootstrapHandler) Parse(args []string, cfg *CmdConfig) error { fs := flag.NewFlagSet("nats-bootstrap", flag.ExitOnError) coreURL := fs.String("core-url", defaultCoreURL, "ServiceRadar core base URL") apiKey := fs.String("api-key", "", "API key for authenticating with core") bearer := fs.String("bearer", "", "Bearer token for authenticating with core") skipTLS := fs.Bool("tls-skip-verify", false, "Skip TLS certificate verification") token := fs.String("token", "", "Platform bootstrap token") outputDir := fs.String("output-dir", defaultNATSOutputDir, "Directory to write NATS configuration files") operatorName := fs.String("operator-name", defaultNATSOperatorName, "Name for the NATS operator") importSeed := fs.String("import-operator-seed", "", "Import existing operator seed instead of generating new") verify := fs.Bool("verify", false, "Verify existing NATS bootstrap configuration") configPath := fs.String("config", "", "Path to existing nats.conf for verification") local := fs.Bool("local", false, "Generate NATS operator and accounts locally without core API") noSystemAccount := fs.Bool("no-system-account", false, "Skip system account generation") jetstream := fs.Bool("jetstream", true, "Enable JetStream") jetstreamDir := fs.String("jetstream-dir", "/var/lib/nats/jetstream", "JetStream storage directory") tlsCert := fs.String("tls-cert", "", "Path to TLS certificate for NATS server") tlsKey := fs.String("tls-key", "", "Path to TLS private key for NATS server") tlsCA := fs.String("tls-ca", "", "Path to TLS CA certificate for client verification") noTLS := fs.Bool("no-tls", false, "Disable TLS for NATS server") writeSystemCreds := fs.Bool("write-system-creds", true, "Write system account creds when available") ... (clipped 141 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=2>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=2>🔴</td> <td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R781-R813'><strong>Silent config drops</strong></a>: Invalid/unsupported checker configs can be silently skipped (returning nil) without any <br>warning/error logging, making misconfigurations difficult to detect and debug.<br> <details open><summary>Referred Code</summary> ```go // protoCheckToCheckerConfig converts a proto AgentCheckConfig to a CheckerConfig. func protoCheckToCheckerConfig(check *proto.AgentCheckConfig) *CheckerConfig { if check == nil { return nil } target := normalizeCheckTarget(check.Target) port := normalizeCheckPort(check.Port) wantHTTPS := check.CheckType == "https" checkerType := mapCheckType(check.CheckType) if (checkerType == tcpCheckType || checkerType == grpcCheckType) && port == 0 { // Allow "host:port" or URL targets that already include a port. hasEmbeddedPort := false if host, p, err := net.SplitHostPort(target); err == nil && host != "" && p != "" { hasEmbeddedPort = true } else if parsed, err := url.Parse(target); err == nil && parsed.Host != "" && parsed.Port() != "" { hasEmbeddedPort = true } else { // Handles "host:port/path" inputs without an explicit scheme. hostPort := target if i := strings.IndexAny(hostPort, "/?"); i >= 0 { ... (clipped 12 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R757-R763'><strong>Sensitive target logging</strong></a>: The code logs <code>check.Target</code> verbatim when applying gateway-provided checks, which could <br>include sensitive data (e.g., URLs with embedded credentials or internal endpoints) and <br>therefore risks leaking secrets into logs.<br> <details open><summary>Referred Code</summary> ```go p.logger.Info(). Str("name", check.Name). Str("type", check.CheckType). Str("target", check.Target). Int32("port", check.Port). Msg("Added/updated check from gateway config") } ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=2>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R545-R706'><strong>Missing audit context</strong></a>: The new enrollment/config-application and status-push flows add operational logs but the <br>diff does not demonstrate audit-grade logging that consistently includes an acting <br>identity and outcome for critical actions across the system.<br> <details open><summary>Referred Code</summary> ```go // enroll sends Hello to the gateway and fetches initial config. func (p *PushLoop) enroll(ctx context.Context) { p.logger.Info().Msg("Enrolling with gateway...") p.initSync(ctx) // Build Hello request p.server.mu.RLock() agentID := p.server.config.AgentID p.server.mu.RUnlock() helloReq := &proto.AgentHelloRequest{ AgentId: agentID, Version: Version, // Agent version from version.go Capabilities: getAgentCapabilities(), ConfigVersion: p.getConfigVersion(), } // Send Hello helloResp, err := p.gateway.Hello(ctx, helloReq) if err != nil { p.logger.Error().Err(err).Msg("Failed to enroll with gateway") return ... (clipped 141 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/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R642-R774'><strong>Gateway config trust</strong></a>: The agent consumes and applies gateway-provided check configuration (targets/paths/ports) <br>and while some validation exists, the diff does not show authentication/authorization and <br>provenance guarantees for this external input.<br> <details open><summary>Referred Code</summary> ```go // fetchAndApplyConfig fetches config from gateway and applies it. func (p *PushLoop) fetchAndApplyConfig(ctx context.Context) { p.server.mu.RLock() agentID := p.server.config.AgentID p.server.mu.RUnlock() configReq := &proto.AgentConfigRequest{ AgentId: agentID, ConfigVersion: p.getConfigVersion(), } configResp, err := p.gateway.GetConfig(ctx, configReq) if err != nil { p.logger.Error().Err(err).Msg("Failed to fetch config from gateway") return } // If config hasn't changed, nothing to do if configResp.NotModified { p.logger.Debug().Str("version", p.getConfigVersion()).Msg("Config not modified") return } ... (clipped 112 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-10 03:56:41 +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/2239#issuecomment-3731786208
Original created: 2026-01-10T03:56:41Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consider a phased feature rollout

Introduce feature flags for major new functionalities like the NATS bootstrap
CLI and the agent push-loop. This enables a safer, incremental rollout in
production by allowing features to be enabled one by one.

Examples:

pkg/core/gateways.go [801-814]
pkg/agent/push_loop.go [230-280]

Solution Walkthrough:

Before:

// In pkg/core/gateways.go
// The new gateway monitoring system is started unconditionally.
func (s *Server) monitorGateways(ctx context.Context) {
	s.logger.Info().
		Msg("Starting gateway monitoring")

	time.Sleep(gatewayDiscoveryTimeout)
	s.checkInitialStates(ctx)

	time.Sleep(gatewayNeverReportedTimeout)
	s.CheckNeverReportedGatewaysStartup(ctx)

	s.MonitorGateways(ctx)
}

// In pkg/agent/push_loop.go
// The new agent push loop is also started unconditionally.
func (p *PushLoop) Start(ctx context.Context) error {
    // ...
    p.logger.Info().Dur("interval", p.getInterval()).Msg("Starting push loop")
    // ... enrollment and push loop logic starts here
}

After:

// In a config file
features:
  gateway_monitoring: true
  agent_push_loop: true
  nats_bootstrap_cli: true

// In pkg/core/gateways.go
// The gateway monitoring system is started based on a feature flag.
func (s *Server) monitorGateways(ctx context.Context) {
    if !s.config.FeatureFlags.GatewayMonitoring {
        s.logger.Info().Msg("Gateway monitoring feature is disabled.")
        return
    }
	s.logger.Info().Msg("Starting gateway monitoring")
    // ... existing logic
	s.MonitorGateways(ctx)
}

// In pkg/agent/push_loop.go
// The agent push loop is started based on a feature flag.
func (p *PushLoop) Start(ctx context.Context) error {
    if !p.server.config.FeatureFlags.AgentPushLoop {
        p.logger.Info().Msg("Agent push loop feature is disabled.")
        return nil // Or handle accordingly
    }
    // ... existing logic
}

Suggestion importance[1-10]: 8

__

Why: The suggestion addresses the significant operational risk of deploying multiple large, independent features at once, which is a critical concern for a PR of this magnitude.

Medium
Possible issue
Robustly merge streaming JSON chunks

Implement a robust method for merging streaming JSON array chunks in
mergeSyncServiceChunks by handling array delimiters and joining with commas to
ensure the final output is a valid JSON array.

pkg/core/gateways.go [1097-1109]

 // mergeSyncServiceChunks concatenates sync service streaming chunks
 func (s *Server) mergeSyncServiceChunks(existingData, newChunk []byte) []byte {
-    // Sync service sends streaming JSON chunks that need simple concatenation
-    // The final reassembled payload will be parsed as a complete JSON structure later
-    existingData = append(existingData, newChunk...)
+    // Trim whitespace to handle formatting variations
+    trimmedExisting := bytes.TrimSpace(existingData)
+    trimmedNew := bytes.TrimSpace(newChunk)
+
+    if len(trimmedExisting) == 0 {
+        return trimmedNew
+    }
+    if len(trimmedNew) == 0 {
+        return trimmedExisting
+    }
+
+    // Robustly merge JSON arrays
+    if bytes.HasSuffix(trimmedExisting, []byte("]")) {
+        trimmedExisting = trimmedExisting[:len(trimmedExisting)-1]
+    }
+    if bytes.HasPrefix(trimmedNew, []byte("[")) {
+        trimmedNew = trimmedNew[1:]
+    }
+
+    // Ensure there's a comma between merged parts if both are non-empty
+    if len(bytes.TrimSpace(trimmedExisting)) > 0 && len(bytes.TrimSpace(trimmedNew)) > 0 {
+        // Check if existing data already ends with a comma
+        if !bytes.HasSuffix(bytes.TrimSpace(trimmedExisting), []byte(",")) {
+            trimmedExisting = append(trimmedExisting, ',')
+        }
+    }
+
+    merged := append(trimmedExisting, trimmedNew...)
+
+    // Wrap in array brackets if not already present
+    if !bytes.HasPrefix(bytes.TrimSpace(merged), []byte("[")) {
+        merged = append([]byte{'['}, merged...)
+    }
+    if !bytes.HasSuffix(bytes.TrimSpace(merged), []byte("]")) {
+        merged = append(merged, ']')
+    }
 
     s.logger.Debug().
-        Int("existing_size", len(existingData)-len(newChunk)).
         Int("new_chunk_size", len(newChunk)).
-        Int("total_size", len(existingData)).
-        Msg("Concatenated sync service chunks")
+        Int("total_size", len(merged)).
+        Msg("Merged sync service chunks")
 
-    return existingData
+    return merged
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that simple byte concatenation can produce invalid JSON, and the proposed change robustly merges JSON array chunks, preventing downstream parsing errors.

Medium
Fix unsupported component type error

Update the CreatePackage function to correctly handle the
EdgeOnboardingComponentTypeSync case, removing the unsupported error and
implementing the necessary logic for package creation.

pkg/core/edge_onboarding.go [875-899]

 case models.EdgeOnboardingComponentTypeChecker:
     if strings.TrimSpace(parentID) == "" {
         return nil, fmt.Errorf("%w: parent_id is required for checker packages", models.ErrEdgeOnboardingInvalidRequest)
     }
     parentType = models.EdgeOnboardingComponentTypeAgent
     resolvedID, err := s.resolveComponentIdentifier(ctx, models.EdgeOnboardingComponentTypeChecker, componentID, label, parentID)
     if err != nil {
         return nil, err
     }
     componentID = resolvedID
     gatewayID = strings.TrimSpace(req.GatewayID)
     if gatewayID == "" {
         resolvedGateway, lookupErr := s.lookupGatewayForAgent(ctx, parentID)
         if lookupErr != nil {
             return nil, lookupErr
         }
         gatewayID = resolvedGateway
     }
 case models.EdgeOnboardingComponentTypeSync:
-    return nil, fmt.Errorf("%w: sync component type not yet supported for edge onboarding", models.ErrEdgeOnboardingInvalidRequest)
+    resolvedGatewayID, err := s.resolveGatewayID(ctx, label, componentID)
+    if err != nil {
+        return nil, err
+    }
+    componentID = resolvedGatewayID
+    gatewayID = resolvedGatewayID
+    parentType = models.EdgeOnboardingComponentTypeNone
+    parentID = ""
+    checkerKind = ""
 case models.EdgeOnboardingComponentTypeNone:
     return nil, fmt.Errorf("%w: component_type is required", models.ErrEdgeOnboardingInvalidRequest)
 default:
     return nil, fmt.Errorf("%w: %s", ErrUnsupportedComponentType, componentType)
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a logical contradiction where a component type is handled in some parts of the code but rejected in the creation logic, and provides a fix to complete the feature.

Medium
Fix stale checks when receiving empty config

Modify applyChecks to correctly handle an empty list of checks from the gateway
by removing all existing checker configurations on the agent.

pkg/agent/push_loop.go [709-773]

 func (p *PushLoop) applyChecks(checks []*proto.AgentCheckConfig) {
-	if len(checks) == 0 {
-		p.logger.Debug().Msg("No checks to apply")
-		return
-	}
-
 	p.server.mu.Lock()
 	defer p.server.mu.Unlock()
 
 	// Track which checks we've seen (for removing stale checks later)
 	seenChecks := make(map[string]bool)
+
+	if len(checks) == 0 {
+		p.logger.Debug().Msg("No checks in new config, removing all existing checks.")
+		// If there are no checks, clear the existing ones.
+		for name := range p.server.checkerConfs {
+			delete(p.server.checkerConfs, name)
+			p.logger.Info().Str("name", name).Msg("Removed stale check")
+		}
+		return
+	}
 
 	for _, check := range checks {
 		// Guard against nil entries in the checks slice
 		if check == nil {
 			continue
 		}
 
 		if !check.Enabled {
 			continue
 		}
 
 		// Require a stable map key for server.checkerConfs.
 		if check.Name == "" {
 			continue
 		}
 
 		seenChecks[check.Name] = true
 
 		// Convert proto check to CheckerConfig
 		checkerConf := protoCheckToCheckerConfig(check)
 		if checkerConf == nil {
 			continue
 		}
 
 		// Check if this config already exists and is unchanged
 		if existing, exists := p.server.checkerConfs[check.Name]; exists && existing != nil {
 			if existing.Type == checkerConf.Type &&
 				existing.Address == checkerConf.Address &&
 				existing.Timeout == checkerConf.Timeout {
 				// Config unchanged, skip
 				continue
 			}
 		}
 
 		// Add or update the checker config
 		p.server.checkerConfs[check.Name] = checkerConf
 
 		p.logger.Info().
 			Str("name", check.Name).
 			Str("type", check.CheckType).
 			Str("target", check.Target).
 			Int32("port", check.Port).
 			Msg("Added/updated check from gateway config")
 	}
 
 	// Remove checks that are no longer in the gateway config.
 	// Gateway config is the source of truth for all checker configurations.
 	for name := range p.server.checkerConfs {
 		if !seenChecks[name] {
 			delete(p.server.checkerConfs, name)
 			p.logger.Info().Str("name", name).Msg("Removed stale check")
 		}
 	}
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a bug where an empty check configuration from the gateway doesn't clear existing checks on the agent, leading to stale checks continuing to run. The fix ensures the agent's configuration accurately mirrors the central configuration.

Medium
Populate GatewayId in request

Populate the GatewayId field in the GatewayStatusRequest with the ID stored in
the server's configuration.

pkg/agent/push_loop.go [349-359]

 req := &proto.GatewayStatusRequest{
     Services:   statuses,
-    GatewayId:  "", // Will be set by the gateway
+    GatewayId:  p.server.config.GatewayID,
     AgentId:    agentID,
     Timestamp:  time.Now().UnixNano(),
     Partition:  partition,
     SourceIp:   p.getSourceIP(),
     KvStoreId:  kvStoreID,
     TenantId:   tenantID,
     TenantSlug: tenantSlug,
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion fixes a significant omission. The GatewayStatusRequest was missing the GatewayId, which is crucial for the receiving gateway to process the status correctly. This change ensures the request is properly formed and functional.

Medium
Persist LastEvaluated timestamp

In checkGatewayStatus, persist the updated LastEvaluated timestamp to the
gatewayStatusCache within a lock to ensure the skip logic functions correctly on
subsequent checks.

pkg/core/gateways.go [110-116]

 // Skip gateways evaluated recently
 if time.Since(ps.LastEvaluated) < defaultSkipInterval {
     continue
 }
 
 // Mark as evaluated
-ps.LastEvaluated = time.Now()
+now := time.Now()
+ps.LastEvaluated = now
+s.cacheMutex.Lock()
+if cached, ok := s.gatewayStatusCache[ps.GatewayID]; ok {
+    cached.LastEvaluated = now
+}
+s.cacheMutex.Unlock()

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the LastEvaluated timestamp is not persisted, causing redundant work, and provides a thread-safe fix to update the shared cache.

Medium
Add missing sweepType constant

Add a sweepType constant to replace the use of the magic string "sweep" for
better code maintainability.

pkg/agent/push_loop.go [122-127]

 const (
-    icmpCheckType = "icmp"
-    tcpCheckType  = "tcp"
-    httpCheckType = "http"
-    grpcCheckType = "grpc"
+    icmpCheckType  = "icmp"
+    tcpCheckType   = "tcp"
+    httpCheckType  = "http"
+    grpcCheckType  = "grpc"
+    sweepType      = "sweep"
 )

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The code uses the magic string "sweep" in multiple places. Adding a sweepType constant improves maintainability and readability by providing a single, authoritative source for this value, reducing the risk of typos.

Low
General
Handle flag parsing failures gracefully

Change the flag.NewFlagSet error handling policy from ExitOnError to
ContinueOnError for more graceful error management.

pkg/cli/nats_bootstrap.go [53-116]

-fs := flag.NewFlagSet("nats-bootstrap", flag.ExitOnError)
+fs := flag.NewFlagSet("nats-bootstrap", flag.ContinueOnError)
 ...
-fs := flag.NewFlagSet("admin nats generate-bootstrap-token", flag.ExitOnError)
+fs := flag.NewFlagSet("admin nats generate-bootstrap-token", flag.ContinueOnError)

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly recommends using flag.ContinueOnError to allow for graceful error handling within the application's command structure, which is a best practice for CLI tools.

Medium
Store gateway ID after enrollment

After successful enrollment, store the GatewayId received from the gateway in
the server's configuration.

pkg/agent/push_loop.go [569-572]

 p.server.mu.Lock()
-p.server.config.TenantID = helloResp.TenantId
-p.server.config.TenantSlug = helloResp.TenantSlug
+p.server.config.TenantID    = helloResp.TenantId
+p.server.config.TenantSlug  = helloResp.TenantSlug
+p.server.config.GatewayID   = helloResp.GatewayId
 p.server.mu.Unlock()

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: This is a good suggestion for improving the agent's state management. Storing the GatewayId after enrollment is necessary for subsequent requests to the gateway, as shown by another valid suggestion that uses this value.

Medium
Sanitize agent ID before validation

In processICMPMetrics, trim whitespace from agentID before checking if it is
empty to improve validation.

pkg/core/metrics.go [579-597]

 func (s *Server) processICMPMetrics(
 	ctx context.Context,
 	gatewayID string, partition string, sourceIP string, agentID string,
 	svc *proto.GatewayServiceStatus,
 	details json.RawMessage,
 	now time.Time) error {
 ...
+	agentID = strings.TrimSpace(agentID)
 	if agentID == "" {
 		s.logger.Warn().
 			Str("gateway_id", gatewayID).
 			Msg("Skipping ICMP metrics without agent ID to avoid gateway device attribution")
 		return nil
 	}
 ...
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential issue where an agentID with only whitespace would bypass validation, leading to data integrity problems. Trimming the agentID before validation is a good practice for robustness.

Low
Default partition constant

In handleGatewayDown, initialize the partition variable with the
defaultPartition constant instead of an empty string to ensure offline events
have a valid partition.

pkg/core/gateways.go [277-280]

 // Emit offline event to NATS if event publisher is available
 if s.eventPublisher != nil {
-    // TODO: Extract source IP and partition from gateway cache if available
+    // TODO: Extract source IP from gateway cache if available
     sourceIP := ""
-    partition := ""
+    partition := defaultPartition

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion improves data consistency by ensuring offline events are published with a default partition value instead of an empty string, aligning with best practices seen elsewhere in the code.

Low
Avoid sending empty optional parameters

In runNatsBootstrapCreate, only include the operator_name in the API request
payload if it has a non-empty value.

pkg/cli/nats_bootstrap.go [174-190]

 func runNatsBootstrapCreate(cfg *CmdConfig) error {
 	if strings.TrimSpace(cfg.NATSBootstrapToken) == "" {
 		return errNATSTokenRequired
 	}
 
 	coreURL := normaliseCoreURL(cfg.CoreAPIURL)
 
 	// Build bootstrap request
 	payload := map[string]interface{}{
 		"token":                   strings.TrimSpace(cfg.NATSBootstrapToken),
-		"operator_name":           cfg.NATSOperatorName,
 		"generate_system_account": !cfg.NATSNoSystemAccount,
+	}
+
+	if operatorName := strings.TrimSpace(cfg.NATSOperatorName); operatorName != "" {
+		payload["operator_name"] = operatorName
 	}
 
 	if seed := strings.TrimSpace(cfg.NATSImportSeed); seed != "" {
 		payload["existing_operator_seed"] = seed
 	}
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that an empty operator_name is sent. While the API might handle this, adding a check improves robustness by making the request payload more explicit and avoiding sending empty optional values.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2239#issuecomment-3731786208 Original created: 2026-01-10T03:56:41Z --- ## PR Code Suggestions ✨ <!-- 2e8a520 --> Explore these optional code suggestions: <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>High-level</td> <td> <details><summary>Consider a phased feature rollout</summary> ___ **Introduce feature flags for major new functionalities like the NATS bootstrap <br>CLI and the agent push-loop. This enables a safer, incremental rollout in <br>production by allowing features to be enabled one by one.** ### Examples: <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R801-R814">pkg/core/gateways.go [801-814]</a> </summary></details> <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R230-R280">pkg/agent/push_loop.go [230-280]</a> </summary></details> ### Solution Walkthrough: #### Before: ```go // In pkg/core/gateways.go // The new gateway monitoring system is started unconditionally. func (s *Server) monitorGateways(ctx context.Context) { s.logger.Info(). Msg("Starting gateway monitoring") time.Sleep(gatewayDiscoveryTimeout) s.checkInitialStates(ctx) time.Sleep(gatewayNeverReportedTimeout) s.CheckNeverReportedGatewaysStartup(ctx) s.MonitorGateways(ctx) } // In pkg/agent/push_loop.go // The new agent push loop is also started unconditionally. func (p *PushLoop) Start(ctx context.Context) error { // ... p.logger.Info().Dur("interval", p.getInterval()).Msg("Starting push loop") // ... enrollment and push loop logic starts here } ``` #### After: ```go // In a config file features: gateway_monitoring: true agent_push_loop: true nats_bootstrap_cli: true // In pkg/core/gateways.go // The gateway monitoring system is started based on a feature flag. func (s *Server) monitorGateways(ctx context.Context) { if !s.config.FeatureFlags.GatewayMonitoring { s.logger.Info().Msg("Gateway monitoring feature is disabled.") return } s.logger.Info().Msg("Starting gateway monitoring") // ... existing logic s.MonitorGateways(ctx) } // In pkg/agent/push_loop.go // The agent push loop is started based on a feature flag. func (p *PushLoop) Start(ctx context.Context) error { if !p.server.config.FeatureFlags.AgentPushLoop { p.logger.Info().Msg("Agent push loop feature is disabled.") return nil // Or handle accordingly } // ... existing logic } ``` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion addresses the significant operational risk of deploying multiple large, independent features at once, which is a critical concern for a PR of this magnitude. </details></details></td><td align=center>Medium </td></tr><tr><td rowspan=6>Possible issue</td> <td> <details><summary>Robustly merge streaming JSON chunks</summary> ___ **Implement a robust method for merging streaming JSON array chunks in <br><code>mergeSyncServiceChunks</code> by handling array delimiters and joining with commas to <br>ensure the final output is a valid JSON array.** [pkg/core/gateways.go [1097-1109]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R1097-R1109) ```diff // mergeSyncServiceChunks concatenates sync service streaming chunks func (s *Server) mergeSyncServiceChunks(existingData, newChunk []byte) []byte { - // Sync service sends streaming JSON chunks that need simple concatenation - // The final reassembled payload will be parsed as a complete JSON structure later - existingData = append(existingData, newChunk...) + // Trim whitespace to handle formatting variations + trimmedExisting := bytes.TrimSpace(existingData) + trimmedNew := bytes.TrimSpace(newChunk) + + if len(trimmedExisting) == 0 { + return trimmedNew + } + if len(trimmedNew) == 0 { + return trimmedExisting + } + + // Robustly merge JSON arrays + if bytes.HasSuffix(trimmedExisting, []byte("]")) { + trimmedExisting = trimmedExisting[:len(trimmedExisting)-1] + } + if bytes.HasPrefix(trimmedNew, []byte("[")) { + trimmedNew = trimmedNew[1:] + } + + // Ensure there's a comma between merged parts if both are non-empty + if len(bytes.TrimSpace(trimmedExisting)) > 0 && len(bytes.TrimSpace(trimmedNew)) > 0 { + // Check if existing data already ends with a comma + if !bytes.HasSuffix(bytes.TrimSpace(trimmedExisting), []byte(",")) { + trimmedExisting = append(trimmedExisting, ',') + } + } + + merged := append(trimmedExisting, trimmedNew...) + + // Wrap in array brackets if not already present + if !bytes.HasPrefix(bytes.TrimSpace(merged), []byte("[")) { + merged = append([]byte{'['}, merged...) + } + if !bytes.HasSuffix(bytes.TrimSpace(merged), []byte("]")) { + merged = append(merged, ']') + } s.logger.Debug(). - Int("existing_size", len(existingData)-len(newChunk)). Int("new_chunk_size", len(newChunk)). - Int("total_size", len(existingData)). - Msg("Concatenated sync service chunks") + Int("total_size", len(merged)). + Msg("Merged sync service chunks") - return existingData + return merged } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies that simple byte concatenation can produce invalid JSON, and the proposed change robustly merges JSON array chunks, preventing downstream parsing errors. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Fix unsupported component type error</summary> ___ **Update the <code>CreatePackage</code> function to correctly handle the <br><code>EdgeOnboardingComponentTypeSync</code> case, removing the unsupported error and <br>implementing the necessary logic for package creation.** [pkg/core/edge_onboarding.go [875-899]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-85874e3c4bdcc9110db09909f10648d44cdee554b26c987f910502321eb20b5cR875-R899) ```diff case models.EdgeOnboardingComponentTypeChecker: if strings.TrimSpace(parentID) == "" { return nil, fmt.Errorf("%w: parent_id is required for checker packages", models.ErrEdgeOnboardingInvalidRequest) } parentType = models.EdgeOnboardingComponentTypeAgent resolvedID, err := s.resolveComponentIdentifier(ctx, models.EdgeOnboardingComponentTypeChecker, componentID, label, parentID) if err != nil { return nil, err } componentID = resolvedID gatewayID = strings.TrimSpace(req.GatewayID) if gatewayID == "" { resolvedGateway, lookupErr := s.lookupGatewayForAgent(ctx, parentID) if lookupErr != nil { return nil, lookupErr } gatewayID = resolvedGateway } case models.EdgeOnboardingComponentTypeSync: - return nil, fmt.Errorf("%w: sync component type not yet supported for edge onboarding", models.ErrEdgeOnboardingInvalidRequest) + resolvedGatewayID, err := s.resolveGatewayID(ctx, label, componentID) + if err != nil { + return nil, err + } + componentID = resolvedGatewayID + gatewayID = resolvedGatewayID + parentType = models.EdgeOnboardingComponentTypeNone + parentID = "" + checkerKind = "" case models.EdgeOnboardingComponentTypeNone: return nil, fmt.Errorf("%w: component_type is required", models.ErrEdgeOnboardingInvalidRequest) default: return nil, fmt.Errorf("%w: %s", ErrUnsupportedComponentType, componentType) } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a logical contradiction where a component type is handled in some parts of the code but rejected in the creation logic, and provides a fix to complete the feature. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Fix stale checks when receiving empty config</summary> ___ **Modify <code>applyChecks</code> to correctly handle an empty list of checks from the gateway <br>by removing all existing checker configurations on the agent.** [pkg/agent/push_loop.go [709-773]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R709-R773) ```diff func (p *PushLoop) applyChecks(checks []*proto.AgentCheckConfig) { - if len(checks) == 0 { - p.logger.Debug().Msg("No checks to apply") - return - } - p.server.mu.Lock() defer p.server.mu.Unlock() // Track which checks we've seen (for removing stale checks later) seenChecks := make(map[string]bool) + + if len(checks) == 0 { + p.logger.Debug().Msg("No checks in new config, removing all existing checks.") + // If there are no checks, clear the existing ones. + for name := range p.server.checkerConfs { + delete(p.server.checkerConfs, name) + p.logger.Info().Str("name", name).Msg("Removed stale check") + } + return + } for _, check := range checks { // Guard against nil entries in the checks slice if check == nil { continue } if !check.Enabled { continue } // Require a stable map key for server.checkerConfs. if check.Name == "" { continue } seenChecks[check.Name] = true // Convert proto check to CheckerConfig checkerConf := protoCheckToCheckerConfig(check) if checkerConf == nil { continue } // Check if this config already exists and is unchanged if existing, exists := p.server.checkerConfs[check.Name]; exists && existing != nil { if existing.Type == checkerConf.Type && existing.Address == checkerConf.Address && existing.Timeout == checkerConf.Timeout { // Config unchanged, skip continue } } // Add or update the checker config p.server.checkerConfs[check.Name] = checkerConf p.logger.Info(). Str("name", check.Name). Str("type", check.CheckType). Str("target", check.Target). Int32("port", check.Port). Msg("Added/updated check from gateway config") } // Remove checks that are no longer in the gateway config. // Gateway config is the source of truth for all checker configurations. for name := range p.server.checkerConfs { if !seenChecks[name] { delete(p.server.checkerConfs, name) p.logger.Info().Str("name", name).Msg("Removed stale check") } } } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: This suggestion correctly identifies a bug where an empty check configuration from the gateway doesn't clear existing checks on the agent, leading to stale checks continuing to run. The fix ensures the agent's configuration accurately mirrors the central configuration. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Populate GatewayId in request</summary> ___ **Populate the <code>GatewayId</code> field in the <code>GatewayStatusRequest</code> with the ID stored in <br>the server's configuration.** [pkg/agent/push_loop.go [349-359]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R349-R359) ```diff req := &proto.GatewayStatusRequest{ Services: statuses, - GatewayId: "", // Will be set by the gateway + GatewayId: p.server.config.GatewayID, AgentId: agentID, Timestamp: time.Now().UnixNano(), Partition: partition, SourceIp: p.getSourceIP(), KvStoreId: kvStoreID, TenantId: tenantID, TenantSlug: tenantSlug, } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: This suggestion fixes a significant omission. The `GatewayStatusRequest` was missing the `GatewayId`, which is crucial for the receiving gateway to process the status correctly. This change ensures the request is properly formed and functional. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Persist LastEvaluated timestamp</summary> ___ **In <code>checkGatewayStatus</code>, persist the updated <code>LastEvaluated</code> timestamp to the <br><code>gatewayStatusCache</code> within a lock to ensure the skip logic functions correctly on <br>subsequent checks.** [pkg/core/gateways.go [110-116]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R110-R116) ```diff // Skip gateways evaluated recently if time.Since(ps.LastEvaluated) < defaultSkipInterval { continue } // Mark as evaluated -ps.LastEvaluated = time.Now() +now := time.Now() +ps.LastEvaluated = now +s.cacheMutex.Lock() +if cached, ok := s.gatewayStatusCache[ps.GatewayID]; ok { + cached.LastEvaluated = now +} +s.cacheMutex.Unlock() ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly points out that the `LastEvaluated` timestamp is not persisted, causing redundant work, and provides a thread-safe fix to update the shared cache. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Add missing sweepType constant</summary> ___ **Add a <code>sweepType</code> constant to replace the use of the magic string "sweep" for <br>better code maintainability.** [pkg/agent/push_loop.go [122-127]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R122-R127) ```diff const ( - icmpCheckType = "icmp" - tcpCheckType = "tcp" - httpCheckType = "http" - grpcCheckType = "grpc" + icmpCheckType = "icmp" + tcpCheckType = "tcp" + httpCheckType = "http" + grpcCheckType = "grpc" + sweepType = "sweep" ) ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 5</summary> __ Why: The code uses the magic string "sweep" in multiple places. Adding a `sweepType` constant improves maintainability and readability by providing a single, authoritative source for this value, reducing the risk of typos. </details></details></td><td align=center>Low </td></tr><tr><td rowspan=5>General</td> <td> <details><summary>Handle flag parsing failures gracefully</summary> ___ **Change the <code>flag.NewFlagSet</code> error handling policy from <code>ExitOnError</code> to <br><code>ContinueOnError</code> for more graceful error management.** [pkg/cli/nats_bootstrap.go [53-116]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457R53-R116) ```diff -fs := flag.NewFlagSet("nats-bootstrap", flag.ExitOnError) +fs := flag.NewFlagSet("nats-bootstrap", flag.ContinueOnError) ... -fs := flag.NewFlagSet("admin nats generate-bootstrap-token", flag.ExitOnError) +fs := flag.NewFlagSet("admin nats generate-bootstrap-token", flag.ContinueOnError) ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly recommends using `flag.ContinueOnError` to allow for graceful error handling within the application's command structure, which is a best practice for CLI tools. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Store gateway ID after enrollment</summary> ___ **After successful enrollment, store the <code>GatewayId</code> received from the gateway in <br>the server's configuration.** [pkg/agent/push_loop.go [569-572]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-5f0d59be34ef26b449d7f5fd2b198a29b277936b9708a699f7487415ed6c2785R569-R572) ```diff p.server.mu.Lock() -p.server.config.TenantID = helloResp.TenantId -p.server.config.TenantSlug = helloResp.TenantSlug +p.server.config.TenantID = helloResp.TenantId +p.server.config.TenantSlug = helloResp.TenantSlug +p.server.config.GatewayID = helloResp.GatewayId p.server.mu.Unlock() ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: This is a good suggestion for improving the agent's state management. Storing the `GatewayId` after enrollment is necessary for subsequent requests to the gateway, as shown by another valid suggestion that uses this value. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Sanitize agent ID before validation</summary> ___ **In <code>processICMPMetrics</code>, trim whitespace from <code>agentID</code> before checking if it is <br>empty to improve validation.** [pkg/core/metrics.go [579-597]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-6d98e853ce17576c088e77956ae4ecfa8078019e0bff107a79d8d1d6ed2443adR579-R597) ```diff func (s *Server) processICMPMetrics( ctx context.Context, gatewayID string, partition string, sourceIP string, agentID string, svc *proto.GatewayServiceStatus, details json.RawMessage, now time.Time) error { ... + agentID = strings.TrimSpace(agentID) if agentID == "" { s.logger.Warn(). Str("gateway_id", gatewayID). Msg("Skipping ICMP metrics without agent ID to avoid gateway device attribution") return nil } ... } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies a potential issue where an `agentID` with only whitespace would bypass validation, leading to data integrity problems. Trimming the `agentID` before validation is a good practice for robustness. </details></details></td><td align=center>Low </td></tr><tr><td> <details><summary>Default partition constant</summary> ___ **In <code>handleGatewayDown</code>, initialize the <code>partition</code> variable with the <br><code>defaultPartition</code> constant instead of an empty string to ensure offline events <br>have a valid partition.** [pkg/core/gateways.go [277-280]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-260332914ad2238e720f4637a71b0f9f01e899102bc6b37f7827782e56b0b5c0R277-R280) ```diff // Emit offline event to NATS if event publisher is available if s.eventPublisher != nil { - // TODO: Extract source IP and partition from gateway cache if available + // TODO: Extract source IP from gateway cache if available sourceIP := "" - partition := "" + partition := defaultPartition ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 4</summary> __ Why: The suggestion improves data consistency by ensuring offline events are published with a default partition value instead of an empty string, aligning with best practices seen elsewhere in the code. </details></details></td><td align=center>Low </td></tr><tr><td> <details><summary>Avoid sending empty optional parameters</summary> ___ **In <code>runNatsBootstrapCreate</code>, only include the <code>operator_name</code> in the API request <br>payload if it has a non-empty value.** [pkg/cli/nats_bootstrap.go [174-190]](https://github.com/carverauto/serviceradar/pull/2239/files#diff-2176d03ba6ab4ccc1b0587a4727171546eecf6123e4df815ead583aaab062457R174-R190) ```diff func runNatsBootstrapCreate(cfg *CmdConfig) error { if strings.TrimSpace(cfg.NATSBootstrapToken) == "" { return errNATSTokenRequired } coreURL := normaliseCoreURL(cfg.CoreAPIURL) // Build bootstrap request payload := map[string]interface{}{ "token": strings.TrimSpace(cfg.NATSBootstrapToken), - "operator_name": cfg.NATSOperatorName, "generate_system_account": !cfg.NATSNoSystemAccount, + } + + if operatorName := strings.TrimSpace(cfg.NATSOperatorName); operatorName != "" { + payload["operator_name"] = operatorName } if seed := strings.TrimSpace(cfg.NATSImportSeed); seed != "" { payload["existing_operator_seed"] = seed } ... ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 4</summary> __ Why: The suggestion correctly points out that an empty `operator_name` is sent. While the API might handle this, adding a check improves robustness by making the request payload more explicit and avoiding sending empty optional values. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!2643
No description provided.