2018 refactor kv store identity map fix #2479

Merged
mfreeman451 merged 6 commits from refs/pull/2479/head into main 2025-11-26 04:33:30 +00:00
mfreeman451 commented 2025-11-26 00:38:49 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2019
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2019
Original created: 2025-11-26T00:38:49Z
Original updated: 2025-11-26T04:33:40Z
Original head: carverauto/serviceradar:2018-refactor-kv-store-identity-map-fix
Original base: main
Original merged: 2025-11-26T04:33:30Z by @mfreeman451

User description

IMPORTANT: Please sign the Developer Certificate of Origin

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

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

Describe your changes

Code checklist before requesting a review

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

PR Type

Enhancement, Tests, Documentation


Description

  • Introduces DeviceIdentityResolver for generating stable ServiceRadar UUIDs using deterministic hashing based on partition and IP, with hierarchical identifier resolution (strong identifiers like MAC, Armis ID, NetBox ID for merging; IP as fallback)

  • Implements cnpgIdentityResolver to enrich device updates with canonical metadata from CNPG unified_devices table, eliminating write amplification from KV store identity publisher

  • Integrates new identity resolution system into device processing pipeline with batch deduplication logic and metadata merging

  • Adds StaleDeviceReaper component to clean up orphaned IP-only devices without strong identifiers on configurable intervals

  • Removes KV-based identity publisher and resolver configuration, replacing with direct CNPG queries for identity resolution

  • Updates sweep result processor and Armis sync to leave DeviceID empty, allowing registry to generate stable ServiceRadar UUIDs

  • Adds database methods for querying stale devices and soft-deleting orphaned records

  • Includes comprehensive test coverage for new identity resolution system, canonicalization scenarios, and device cleanup

  • Provides detailed documentation of architecture findings, implementation plan, and deployment history

  • Includes database migration to merge duplicate devices in unified_devices table


Diagram Walkthrough

flowchart LR
  A["Sweep Results"] --> B["Result Processor"]
  C["Armis Sync"] --> D["Armis Devices"]
  B --> E["Empty DeviceID"]
  D --> E
  E --> F["DeviceIdentityResolver"]
  F --> G["Stable ServiceRadar UUID"]
  G --> H["Registry Pipeline"]
  H --> I["CNPGIdentityResolver"]
  I --> J["Canonical Metadata"]
  J --> K["Unified Devices Table"]
  K --> L["StaleDeviceReaper"]
  L --> M["Soft Delete Orphaned Devices"]

File Walkthrough

Relevant files
Enhancement
10 files
device_identity.go
New device identity resolver with stable UUID generation 

pkg/registry/device_identity.go

  • Introduces DeviceIdentityResolver to generate stable ServiceRadar
    UUIDs for devices using deterministic hashing based on partition and
    IP
  • Implements hierarchical identifier resolution: strong identifiers
    (MAC, Armis ID, NetBox ID) for device merging, weak identifiers (IP)
    as fallback
  • Provides batch and single device ID resolution with in-memory caching
    (5-minute TTL, 100k entries)
  • Includes database query methods to find existing devices by MAC, Armis
    ID, and NetBox ID with filtering for legacy IDs
+983/-0 
identity_resolver_cnpg.go
CNPG-based identity resolver for canonical metadata hydration

pkg/registry/identity_resolver_cnpg.go

  • Introduces cnpgIdentityResolver for enriching device updates with
    canonical metadata from CNPG unified_devices table
  • Implements in-memory cache for IP-to-device-ID mappings and device
    metadata with TTL-based expiry
  • Provides hydrateCanonical method to apply canonical identifiers and
    metadata to device updates
  • Eliminates write amplification from KV store identity publisher by
    using direct CNPG queries
+366/-0 
registry.go
Registry integration of new identity resolution system     

pkg/registry/registry.go

  • Integrates DeviceIdentityResolver and cnpgIdentityResolver into device
    processing pipeline
  • Adds batch deduplication logic to prevent duplicate updates for same
    IP within a batch
  • Implements metadata merging for duplicate updates
  • Updates canonicalIDCandidate to skip legacy partition:IP format IDs
    and prefer ServiceRadar UUIDs
+118/-31
reaper.go
Stale device reaper for cleanup of orphaned devices           

pkg/core/reaper.go

  • New StaleDeviceReaper component to clean up orphaned IP-only devices
    without strong identifiers
  • Runs on configurable interval (default 1 hour) and soft-deletes
    devices older than TTL (default 24 hours)
  • Prevents unlimited growth of stale devices from DHCP churn
+97/-0   
server.go
Server initialization with new identity resolution system

pkg/core/server.go

  • Removes KV-based identity publisher and resolver configuration (caused
    write amplification)
  • Initializes DeviceIdentityResolver and CNPGIdentityResolver as primary
    identity resolution mechanism
  • Starts StaleDeviceReaper background loop on server startup
  • Updates comments explaining shift from KV to CNPG for identity
    resolution
+18/-10 
result_processor.go
Sweep processor uses empty device IDs for resolver             

pkg/core/result_processor.go

  • Changes sweep result processing to leave DeviceID empty instead of
    generating legacy partition:IP format
  • Allows DeviceIdentityResolver to generate stable ServiceRadar UUIDs
+1/-1     
devices.go
Armis sync uses empty device IDs for resolver                       

pkg/sync/integrations/armis/devices.go

  • Changes Armis device update to leave DeviceID empty instead of
    partition:IP format
  • Stores Armis device ID in metadata as strong identifier for device
    merging
  • Allows registry to generate stable ServiceRadar UUIDs
+7/-3     
cnpg_unified_devices.go
Database methods for stale device identification and soft deletion

pkg/db/cnpg_unified_devices.go

  • Adds GetStaleIPOnlyDevices method to query devices without strong
    identifiers older than TTL
  • Adds SoftDeleteDevices method to mark devices as deleted by updating
    metadata
  • Filters out deleted and merged devices in queries
+61/-0   
interfaces.go
Service interface additions for device cleanup                     

pkg/db/interfaces.go

  • Adds GetStaleIPOnlyDevices and SoftDeleteDevices method signatures to
    Service interface
+2/-0     
main.go
Faker simulation warmup cycles for IP churn                           

cmd/faker/main.go

  • Adds WarmupCycles configuration parameter to IP shuffle simulation
  • Implements warmup phase where IP changes are skipped for initial
    cycles
  • Allows simulation to stabilize before introducing DHCP churn
+23/-4   
Tests
6 files
registry_test.go
Test updates for ServiceRadar UUID generation                       

pkg/registry/registry_test.go

  • Updates test expectations to verify ServiceRadar UUID generation (sr:
    prefix) instead of legacy partition:IP format
  • Adds WithDeviceIdentityResolver option to registry initialization in
    tests
  • Verifies that different devices get different UUIDs within batch
    processing
  • Removes tombstone expectations from canonicalization tests
+25/-22 
canon_simulation_test.go
Canonicalization simulation tests for real-world scenarios

pkg/registry/canon_simulation_test.go

  • New test file simulating real-world device discovery scenarios (sweep
    followed by Armis sync)
  • Tests DHCP churn handling where devices change IPs and strong
    identifiers arrive late
  • Verifies that devices are correctly merged when strong identifiers
    match
  • Demonstrates orphan device problem that the Reaper is designed to
    solve
+252/-0 
device_identity_test.go
Device identity resolver unit tests                                           

pkg/registry/device_identity_test.go

  • New test verifying IP fallback behavior when strong identifiers are
    unknown
  • Tests that device updates resolve to existing devices by IP when no
    strong ID matches exist
+62/-0   
identity_resolver_cnpg_test.go
CNPG identity resolver unit tests                                               

pkg/registry/identity_resolver_cnpg_test.go

  • Tests CNPG identity resolver IP resolution with caching behavior
  • Verifies cache hit/miss and expiry mechanics
  • Tests canonical metadata hydration from unified devices
  • Validates cache eviction when capacity is exceeded
+251/-0 
reaper_test.go
Stale device reaper unit tests                                                     

pkg/core/reaper_test.go

  • Unit tests for StaleDeviceReaper covering success cases, empty
    results, and error handling
  • Verifies correct interaction with database methods for querying and
    soft-deleting devices
+85/-0   
armis_test.go
Armis integration test updates for empty device IDs           

pkg/sync/integrations/armis/armis_test.go

  • Updates test expectations to verify empty DeviceID in Armis events
    instead of partition:IP format
  • Reflects change that registry will generate ServiceRadar UUIDs
+2/-2     
Miscellaneous
1 files
mock_db.go
Mock database regeneration with new methods                           

pkg/db/mock_db.go

  • Regenerates mock with updated mockgen command format (module path
    instead of source file)
  • Removes MockQueryExecutor interface mock (no longer needed)
  • Adds mock implementations for GetStaleIPOnlyDevices and
    SoftDeleteDevices methods
+32/-301
Configuration changes
2 files
00000000000007_merge_duplicates.up.sql
Database migration to merge duplicate devices                       

pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql

  • New database migration to merge duplicate devices in unified_devices
    table
  • Identifies duplicates sharing same IP and picks canonical device based
    on strong identifiers
  • Updates duplicate records with _merged_into metadata pointing to
    canonical device
+90/-0   
values.yaml
Helm values update with new service versions                         

helm/serviceradar/values.yaml

  • Updates core service image tag to specific commit SHA with
    ServiceRadar UUID identity system
  • Updates sync service image tag to specific commit SHA with Armis sync
    empty DeviceID changes
  • Adds explanatory comments about identity system changes
+4/-2     
Documentation
3 files
device_canon_findings.md
Device canonicalization architecture findings documentation

device_canon_findings.md

  • Documents investigation findings on device UUID generation
    inconsistencies
  • Explains root cause: different UUIDs generated for same device
    discovered via sweep vs Armis
  • Recommends implementation of deterministic IP-based UUID generation
  • Outlines plan for legacy ID cleanup and batch deduplication
+33/-0   
kv_fix.md
KV Store Identity Map Fix Implementation and Deployment History

kv_fix.md

  • Comprehensive documentation of KV Store Identity Map Fix Plan with
    three completed phases (CNPG resolver, ServiceRadar UUID system,
    legacy ID migration)
  • Details implementation of DeviceIdentityResolver with in-memory cache,
    UUID generation, and strong/weak identifier handling
  • Documents deployment history, test results, and current issues with
    device count growth (50k target vs 93k actual)
  • Includes architectural diagrams, success metrics, and next steps for
    investigation and potential fixes
+441/-0 
device_canon_plan.md
Device Canonicalization Fix Plan and Root Cause Analysis 

device_canon_plan.md

  • Detailed analysis of device canonicalization issues causing 11k
    duplicate devices (61.1k actual vs 50k expected)
  • Identifies root causes: UUID generation inconsistency, legacy DeviceID
    format, race conditions between sweep and Armis sync, and cache
    invalidation gaps
  • Proposes five-phase solution including IP-based deterministic UUID
    generation, sweep result processor fixes, and merge-only strong
    identifiers
  • Provides implementation checklist, risk assessment, database cleanup
    SQL, and timeline for fixes
+499/-0 

Imported from GitHub pull request. Original GitHub pull request: #2019 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2019 Original created: 2025-11-26T00:38:49Z Original updated: 2025-11-26T04:33:40Z Original head: carverauto/serviceradar:2018-refactor-kv-store-identity-map-fix Original base: main Original merged: 2025-11-26T04:33:30Z by @mfreeman451 --- ### **User description** ## IMPORTANT: Please sign the Developer Certificate of Origin Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include a [DCO sign-off statement]( https://developercertificate.org/) indicating the DCO acceptance in one commit message. Here is an example DCO Signed-off-by line in a commit message: ``` Signed-off-by: J. Doe <j.doe@domain.com> ``` ## Describe your changes ## Issue ticket number and link ## Code checklist before requesting a review - [ ] I have signed the DCO? - [ ] The build completes without errors? - [ ] All tests are passing when running make test? ___ ### **PR Type** Enhancement, Tests, Documentation ___ ### **Description** - Introduces `DeviceIdentityResolver` for generating stable ServiceRadar UUIDs using deterministic hashing based on partition and IP, with hierarchical identifier resolution (strong identifiers like MAC, Armis ID, NetBox ID for merging; IP as fallback) - Implements `cnpgIdentityResolver` to enrich device updates with canonical metadata from CNPG unified_devices table, eliminating write amplification from KV store identity publisher - Integrates new identity resolution system into device processing pipeline with batch deduplication logic and metadata merging - Adds `StaleDeviceReaper` component to clean up orphaned IP-only devices without strong identifiers on configurable intervals - Removes KV-based identity publisher and resolver configuration, replacing with direct CNPG queries for identity resolution - Updates sweep result processor and Armis sync to leave `DeviceID` empty, allowing registry to generate stable ServiceRadar UUIDs - Adds database methods for querying stale devices and soft-deleting orphaned records - Includes comprehensive test coverage for new identity resolution system, canonicalization scenarios, and device cleanup - Provides detailed documentation of architecture findings, implementation plan, and deployment history - Includes database migration to merge duplicate devices in unified_devices table ___ ### Diagram Walkthrough ```mermaid flowchart LR A["Sweep Results"] --> B["Result Processor"] C["Armis Sync"] --> D["Armis Devices"] B --> E["Empty DeviceID"] D --> E E --> F["DeviceIdentityResolver"] F --> G["Stable ServiceRadar UUID"] G --> H["Registry Pipeline"] H --> I["CNPGIdentityResolver"] I --> J["Canonical Metadata"] J --> K["Unified Devices Table"] K --> L["StaleDeviceReaper"] L --> M["Soft Delete Orphaned Devices"] ``` <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>10 files</summary><table> <tr> <td> <details> <summary><strong>device_identity.go</strong><dd><code>New device identity resolver with stable UUID generation</code>&nbsp; </dd></summary> <hr> pkg/registry/device_identity.go <ul><li>Introduces <code>DeviceIdentityResolver</code> to generate stable ServiceRadar <br>UUIDs for devices using deterministic hashing based on partition and <br>IP<br> <li> Implements hierarchical identifier resolution: strong identifiers <br>(MAC, Armis ID, NetBox ID) for device merging, weak identifiers (IP) <br>as fallback<br> <li> Provides batch and single device ID resolution with in-memory caching <br>(5-minute TTL, 100k entries)<br> <li> Includes database query methods to find existing devices by MAC, Armis <br>ID, and NetBox ID with filtering for legacy IDs</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4e">+983/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>identity_resolver_cnpg.go</strong><dd><code>CNPG-based identity resolver for canonical metadata hydration</code></dd></summary> <hr> pkg/registry/identity_resolver_cnpg.go <ul><li>Introduces <code>cnpgIdentityResolver</code> for enriching device updates with <br>canonical metadata from CNPG unified_devices table<br> <li> Implements in-memory cache for IP-to-device-ID mappings and device <br>metadata with TTL-based expiry<br> <li> Provides <code>hydrateCanonical</code> method to apply canonical identifiers and <br>metadata to device updates<br> <li> Eliminates write amplification from KV store identity publisher by <br>using direct CNPG queries</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-e758ca8be15d52379ccb94d2b35754f96d2d4d6e80a0f7d0b4a9035c9572518f">+366/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>registry.go</strong><dd><code>Registry integration of new identity resolution system</code>&nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/registry.go <ul><li>Integrates <code>DeviceIdentityResolver</code> and <code>cnpgIdentityResolver</code> into device <br>processing pipeline<br> <li> Adds batch deduplication logic to prevent duplicate updates for same <br>IP within a batch<br> <li> Implements metadata merging for duplicate updates<br> <li> Updates <code>canonicalIDCandidate</code> to skip legacy partition:IP format IDs <br>and prefer ServiceRadar UUIDs</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-cb61d8f79451b9541de4a8cc0811523a68d15452b2f5971c7618ea5b423cf4ec">+118/-31</a></td> </tr> <tr> <td> <details> <summary><strong>reaper.go</strong><dd><code>Stale device reaper for cleanup of orphaned devices</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/core/reaper.go <ul><li>New <code>StaleDeviceReaper</code> component to clean up orphaned IP-only devices <br>without strong identifiers<br> <li> Runs on configurable interval (default 1 hour) and soft-deletes <br>devices older than TTL (default 24 hours)<br> <li> Prevents unlimited growth of stale devices from DHCP churn</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-f726319fea6a381a430fc9d50a4cecc5b0d908e404f0b36f4f71ca968c1f7486">+97/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>server.go</strong><dd><code>Server initialization with new identity resolution system</code></dd></summary> <hr> pkg/core/server.go <ul><li>Removes KV-based identity publisher and resolver configuration (caused <br>write amplification)<br> <li> Initializes <code>DeviceIdentityResolver</code> and <code>CNPGIdentityResolver</code> as primary <br>identity resolution mechanism<br> <li> Starts <code>StaleDeviceReaper</code> background loop on server startup<br> <li> Updates comments explaining shift from KV to CNPG for identity <br>resolution</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-a12d34beb8640efbbedad28f8610fd58afeea457572d82e4fd295ab2a6bb8ee6">+18/-10</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>result_processor.go</strong><dd><code>Sweep processor uses empty device IDs for resolver</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/core/result_processor.go <ul><li>Changes sweep result processing to leave <code>DeviceID</code> empty instead of <br>generating legacy partition:IP format<br> <li> Allows <code>DeviceIdentityResolver</code> to generate stable ServiceRadar UUIDs</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-828f49c36998b59e7eea7ceca2e1982ce06366bca483c9a52439a360ce2a0a50">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>devices.go</strong><dd><code>Armis sync uses empty device IDs for resolver</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/sync/integrations/armis/devices.go <ul><li>Changes Armis device update to leave <code>DeviceID</code> empty instead of <br>partition:IP format<br> <li> Stores Armis device ID in metadata as strong identifier for device <br>merging<br> <li> Allows registry to generate stable ServiceRadar UUIDs</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-5c4b3f031aab83e30ef6f9134b12c9a85088a59e3c8d8c7d74edd144bfd15409">+7/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>cnpg_unified_devices.go</strong><dd><code>Database methods for stale device identification and soft deletion</code></dd></summary> <hr> pkg/db/cnpg_unified_devices.go <ul><li>Adds <code>GetStaleIPOnlyDevices</code> method to query devices without strong <br>identifiers older than TTL<br> <li> Adds <code>SoftDeleteDevices</code> method to mark devices as deleted by updating <br>metadata<br> <li> Filters out deleted and merged devices in queries</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-ab3bf557cf9bb1b281a315a73abee38748de1654941c2471542e5e9bfc1716d8">+61/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>interfaces.go</strong><dd><code>Service interface additions for device cleanup</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/interfaces.go <ul><li>Adds <code>GetStaleIPOnlyDevices</code> and <code>SoftDeleteDevices</code> method signatures to <br>Service interface</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-c230fe0c315251837357bfde4ae7f7b34080398d8e48af6bf78badb2124271f3">+2/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>Faker simulation warmup cycles for IP churn</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/faker/main.go <ul><li>Adds <code>WarmupCycles</code> configuration parameter to IP shuffle simulation<br> <li> Implements warmup phase where IP changes are skipped for initial <br>cycles<br> <li> Allows simulation to stabilize before introducing DHCP churn</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-f101715b3bafbe3843ac9df0ffa3566fd0278b8f730efa3665557ce98acf6d23">+23/-4</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>6 files</summary><table> <tr> <td> <details> <summary><strong>registry_test.go</strong><dd><code>Test updates for ServiceRadar UUID generation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/registry_test.go <ul><li>Updates test expectations to verify ServiceRadar UUID generation (sr: <br>prefix) instead of legacy partition:IP format<br> <li> Adds <code>WithDeviceIdentityResolver</code> option to registry initialization in <br>tests<br> <li> Verifies that different devices get different UUIDs within batch <br>processing<br> <li> Removes tombstone expectations from canonicalization tests</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-f010972d104404be52d2a8e6e784cb56e31194f90795a69571a12696bcbdc075">+25/-22</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>canon_simulation_test.go</strong><dd><code>Canonicalization simulation tests for real-world scenarios</code></dd></summary> <hr> pkg/registry/canon_simulation_test.go <ul><li>New test file simulating real-world device discovery scenarios (sweep <br>followed by Armis sync)<br> <li> Tests DHCP churn handling where devices change IPs and strong <br>identifiers arrive late<br> <li> Verifies that devices are correctly merged when strong identifiers <br>match<br> <li> Demonstrates orphan device problem that the Reaper is designed to <br>solve</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-73b73409b3941af4ac860561c87772763feca734fd8ab597f36e5e4047c6bf3c">+252/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>device_identity_test.go</strong><dd><code>Device identity resolver unit tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/device_identity_test.go <ul><li>New test verifying IP fallback behavior when strong identifiers are <br>unknown<br> <li> Tests that device updates resolve to existing devices by IP when no <br>strong ID matches exist</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-af686666ccf08b175566323b1044ac383d7a8ce54ffdcbcbd828385df1e032a3">+62/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>identity_resolver_cnpg_test.go</strong><dd><code>CNPG identity resolver unit tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/identity_resolver_cnpg_test.go <ul><li>Tests CNPG identity resolver IP resolution with caching behavior<br> <li> Verifies cache hit/miss and expiry mechanics<br> <li> Tests canonical metadata hydration from unified devices<br> <li> Validates cache eviction when capacity is exceeded</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-9af43f5d2677c38f3515024e4268531f4125e4368719ae4725c830ef1f89eec2">+251/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>reaper_test.go</strong><dd><code>Stale device reaper unit tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/core/reaper_test.go <ul><li>Unit tests for <code>StaleDeviceReaper</code> covering success cases, empty <br>results, and error handling<br> <li> Verifies correct interaction with database methods for querying and <br>soft-deleting devices</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-05d0ad601fa303f61c6a5664ad928a0579844f3ebe0c599dfea4260bbcd27d1a">+85/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>armis_test.go</strong><dd><code>Armis integration test updates for empty device IDs</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/sync/integrations/armis/armis_test.go <ul><li>Updates test expectations to verify empty <code>DeviceID</code> in Armis events <br>instead of partition:IP format<br> <li> Reflects change that registry will generate ServiceRadar UUIDs</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-69a98265fabfd3234d96f407cd3ce5e716245079318a624db61f55c1c9b17571">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Miscellaneous</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>mock_db.go</strong><dd><code>Mock database regeneration with new methods</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/mock_db.go <ul><li>Regenerates mock with updated mockgen command format (module path <br>instead of source file)<br> <li> Removes <code>MockQueryExecutor</code> interface mock (no longer needed)<br> <li> Adds mock implementations for <code>GetStaleIPOnlyDevices</code> and <br><code>SoftDeleteDevices</code> methods</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-30e38f888d4849fc40d7ebb1559c2a84c43aa8cd13b3b89fd7ec6cf873b243c7">+32/-301</a></td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>2 files</summary><table> <tr> <td> <details> <summary><strong>00000000000007_merge_duplicates.up.sql</strong><dd><code>Database migration to merge duplicate devices</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql <ul><li>New database migration to merge duplicate devices in unified_devices <br>table<br> <li> Identifies duplicates sharing same IP and picks canonical device based <br>on strong identifiers<br> <li> Updates duplicate records with <code>_merged_into</code> metadata pointing to <br>canonical device</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-4dfc8968de91ccfe580087aea2962c4d3563814649f414623f148bebeec6a990">+90/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>values.yaml</strong><dd><code>Helm values update with new service versions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> helm/serviceradar/values.yaml <ul><li>Updates core service image tag to specific commit SHA with <br>ServiceRadar UUID identity system<br> <li> Updates sync service image tag to specific commit SHA with Armis sync <br>empty DeviceID changes<br> <li> Adds explanatory comments about identity system changes</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-d4449c7cb70362554b274f81eae5a4b81a8e81df494282e383d1b7ea3871c452">+4/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>3 files</summary><table> <tr> <td> <details> <summary><strong>device_canon_findings.md</strong><dd><code>Device canonicalization architecture findings documentation</code></dd></summary> <hr> device_canon_findings.md <ul><li>Documents investigation findings on device UUID generation <br>inconsistencies<br> <li> Explains root cause: different UUIDs generated for same device <br>discovered via sweep vs Armis<br> <li> Recommends implementation of deterministic IP-based UUID generation<br> <li> Outlines plan for legacy ID cleanup and batch deduplication</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-9901537bc6c952b8c20a11289004d2e7cacf003a44d0d9d290fc114ec05ce52f">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>kv_fix.md</strong><dd><code>KV Store Identity Map Fix Implementation and Deployment History</code></dd></summary> <hr> kv_fix.md <ul><li>Comprehensive documentation of KV Store Identity Map Fix Plan with <br>three completed phases (CNPG resolver, ServiceRadar UUID system, <br>legacy ID migration)<br> <li> Details implementation of <code>DeviceIdentityResolver</code> with in-memory cache, <br>UUID generation, and strong/weak identifier handling<br> <li> Documents deployment history, test results, and current issues with <br>device count growth (50k target vs 93k actual)<br> <li> Includes architectural diagrams, success metrics, and next steps for <br>investigation and potential fixes</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-c148df157c91809d994e107aff870b5e005fbff5be6a329b34721a2950b70627">+441/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>device_canon_plan.md</strong><dd><code>Device Canonicalization Fix Plan and Root Cause Analysis</code>&nbsp; </dd></summary> <hr> device_canon_plan.md <ul><li>Detailed analysis of device canonicalization issues causing 11k <br>duplicate devices (61.1k actual vs 50k expected)<br> <li> Identifies root causes: UUID generation inconsistency, legacy DeviceID <br>format, race conditions between sweep and Armis sync, and cache <br>invalidation gaps<br> <li> Proposes five-phase solution including IP-based deterministic UUID <br>generation, sweep result processor fixes, and merge-only strong <br>identifiers<br> <li> Provides implementation checklist, risk assessment, database cleanup <br>SQL, and timeline for fixes</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-68291d1e52fcc6df002cb82f6377249f029c371c423e78b1d1b983bc8ab3f974">+499/-0</a>&nbsp; </td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2025-11-26 00:39:48 +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/2019#issuecomment-3578227891
Original created: 2025-11-26T00:39:48Z

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Predictable identifier generation

Description: The deterministic "sr:" device ID is derived solely from partition and IP, allowing an
attacker who can spoof or obtain the same IP in a partition to predict or collide on
device IDs and potentially cause cross-device association or confusion.
device_identity.go [846-882]

Referred Code
func generateServiceRadarDeviceID(update *models.DeviceUpdate) string {
	// Create a deterministic seed from available identifiers
	h := sha256.New()
	h.Write([]byte("serviceradar-device-v2:"))

	// Primary identity anchor
	ip := strings.TrimSpace(update.IP)
	partition := strings.TrimSpace(update.Partition)
	if partition == "" {
		partition = "default"
	}

	// UUID is ALWAYS based on IP + partition
	// This ensures determinism regardless of discovery order
	h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip)))

	// Add timestamp for uniqueness if no identifiers
	hashBytes := h.Sum(nil)
	if len(hashBytes) < 16 {
		// Fallback to random UUID if hash is too short
		return "sr:" + uuid.New().String()


 ... (clipped 16 lines)
SQL injection risk

Description: Direct SQL strings with string pattern matching (LIKE and JSON operators) are executed via
ExecuteQuery; while parameters are bound for arrays, ensure all inputs are parameterized
to avoid SQL injection risks from any untrusted string lists or metadata-driven filters.
device_identity.go [660-741]

Referred Code
SELECT DISTINCT ON (mac) mac, device_id
FROM unified_devices
WHERE mac = ANY($1)
  AND device_id LIKE 'sr:%'
  AND (metadata->>'_merged_into' IS NULL OR metadata->>'_merged_into' = '' OR metadata->>'_merged_into' = device_id)
  AND COALESCE(lower(metadata->>'_deleted'),'false') <> 'true'
  AND COALESCE(lower(metadata->>'deleted'),'false') <> 'true'
ORDER BY mac, last_seen DESC`

	rows, err := r.db.ExecuteQuery(ctx, query, macs)
	if err != nil {
		return nil, err
	}

	result := make(map[string]string, len(rows))
	for _, row := range rows {
		rawMAC, _ := row["mac"].(string)
		deviceID, _ := row["device_id"].(string)
		deviceID = strings.TrimSpace(deviceID)
		if rawMAC == "" || deviceID == "" || isLegacyIPBasedID(deviceID) {
			continue


 ... (clipped 61 lines)
Ticket Compliance
🟡
🎫 #2018
🟢 Replace KV-based identity publisher/resolver with a system that generates stable
ServiceRadar UUIDs via deterministic identity resolution prioritizing strong identifiers
(MAC, Armis ID, NetBox ID) and using IP as a weak fallback.
Integrate a CNPG-based identity resolver that queries unified_devices to enrich updates
with canonical metadata and reduce write amplification.
Integrate the new identity resolution into the registry/device processing pipeline,
including batch deduplication and metadata merging.
Update sweep result processor and Armis sync to leave DeviceID empty so the registry
assigns stable ServiceRadar UUIDs.
Add database methods to query stale devices and soft-delete orphaned records.
Provide comprehensive tests covering identity resolution, canonicalization, and cleanup
flows.
Update documentation/architecture findings and include a migration to merge duplicate
devices in unified_devices.
Add a StaleDeviceReaper to remove orphaned IP-only devices without strong identifiers on a
configurable interval.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

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

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

Status: Passed

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

Generic: Secure Error Handling

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

Status: Passed

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

Generic: Secure Logging Practices

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

Status: Passed

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

Generic: Comprehensive Audit Trails

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

Status:
Limited Auditing: New identity resolution generates and assigns canonical IDs and performs merges/lookups
without explicit audit logging of these critical actions, making full auditability
uncertain from the diff alone.

Referred Code
func (r *DeviceIdentityResolver) ResolveDeviceID(ctx context.Context, update *models.DeviceUpdate) (string, error) {
	if r == nil || r.db == nil {
		// Fallback to existing behavior if resolver not configured
		return update.DeviceID, nil
	}

	// Skip service component IDs - they use a different identity scheme
	if isServiceDeviceID(update.DeviceID) {
		return update.DeviceID, nil
	}

	// Extract identifiers from update
	identifiers := r.extractIdentifiers(update)

	// Check cache first for strong identifiers
	if deviceID := r.checkCacheForStrongIdentifiers(identifiers); deviceID != "" {
		return deviceID, nil
	}

	// Prefer strong identifier matches even when IP differs
	if matches, err := r.findExistingDevicesByStrongIdentifiers(ctx,


 ... (clipped 52 lines)

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

Generic: Robust Error Handling and Edge Case Management

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

Status:
Swallowed Errors: Several database lookup errors are downgraded to debug logs and resolution proceeds (e.g.,
generating new IDs) which may hide operational issues without clear propagation or
metrics.

Referred Code
existingDeviceID, err := r.findExistingDevice(ctx, identifiers, true)
if err != nil {
	r.logger.Debug().Err(err).Msg("Failed to find existing device, will generate new ID")
}

if existingDeviceID != "" {
	// Cache the mapping for future lookups
	r.cacheIdentifierMappings(identifiers, existingDeviceID)
	return existingDeviceID, nil
}

// Check if the update already has a ServiceRadar UUID
if isServiceRadarUUID(update.DeviceID) {
	r.cacheIdentifierMappings(identifiers, update.DeviceID)
	return update.DeviceID, nil
}

// Check cache for weak identifier (IP)
// We check this even if strong identifiers are present, because we want to
// reuse the existing UUID for this IP if one exists.
for _, ip := range identifiers.IPs {


 ... (clipped 19 lines)

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

Generic: Security-First Input Validation and Data Handling

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

Status:
Input Trust: The resolver trusts metadata fields like 'armis_device_id' and
'integration_id' from updates without visible validation or normalization beyond
trimming, which may require upstream validation guarantees.

Referred Code
func (r *DeviceIdentityResolver) extractIdentifiers(update *models.DeviceUpdate) *deviceIdentifiers {
	ids := &deviceIdentifiers{
		IP:         strings.TrimSpace(update.IP),
		ExistingID: strings.TrimSpace(update.DeviceID),
	}

	if update.MAC != nil {
		ids.MAC = normalizeMAC(*update.MAC)
	}

	if update.Metadata != nil {
		if armisID := strings.TrimSpace(update.Metadata["armis_device_id"]); armisID != "" {
			ids.ArmisID = armisID
		}
		if netboxID := strings.TrimSpace(update.Metadata["netbox_device_id"]); netboxID != "" {
			ids.NetboxID = netboxID
		}
		// Also check integration_id for netbox type
		if update.Metadata["integration_type"] == "netbox" {
			if intID := strings.TrimSpace(update.Metadata["integration_id"]); intID != "" {
				ids.NetboxID = intID


 ... (clipped 33 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/2019#issuecomment-3578227891 Original created: 2025-11-26T00:39:48Z --- _You are nearing your monthly Qodo Merge usage quota. For more information, please visit [here](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/#cloud-users)._ ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/34d5dc2a9583f0a3a3a7e3a580437d25609f6412 --> Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=2>⚪</td> <td><details><summary><strong>Predictable identifier generation </strong></summary><br> <b>Description:</b> The deterministic "sr:" device ID is derived solely from partition and IP, allowing an <br>attacker who can spoof or obtain the same IP in a partition to predict or collide on <br>device IDs and potentially cause cross-device association or confusion.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR846-R882'>device_identity.go [846-882]</a></strong><br> <details open><summary>Referred Code</summary> ```go func generateServiceRadarDeviceID(update *models.DeviceUpdate) string { // Create a deterministic seed from available identifiers h := sha256.New() h.Write([]byte("serviceradar-device-v2:")) // Primary identity anchor ip := strings.TrimSpace(update.IP) partition := strings.TrimSpace(update.Partition) if partition == "" { partition = "default" } // UUID is ALWAYS based on IP + partition // This ensures determinism regardless of discovery order h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip))) // Add timestamp for uniqueness if no identifiers hashBytes := h.Sum(nil) if len(hashBytes) < 16 { // Fallback to random UUID if hash is too short return "sr:" + uuid.New().String() ... (clipped 16 lines) ``` </details></details></td></tr> <tr><td><details><summary><strong>SQL injection risk</strong></summary><br> <b>Description:</b> Direct SQL strings with string pattern matching (LIKE and JSON operators) are executed via <br>ExecuteQuery; while parameters are bound for arrays, ensure all inputs are parameterized <br>to avoid SQL injection risks from any untrusted string lists or metadata-driven filters.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR660-R741'>device_identity.go [660-741]</a></strong><br> <details open><summary>Referred Code</summary> ```go SELECT DISTINCT ON (mac) mac, device_id FROM unified_devices WHERE mac = ANY($1) AND device_id LIKE 'sr:%' AND (metadata->>'_merged_into' IS NULL OR metadata->>'_merged_into' = '' OR metadata->>'_merged_into' = device_id) AND COALESCE(lower(metadata->>'_deleted'),'false') <> 'true' AND COALESCE(lower(metadata->>'deleted'),'false') <> 'true' ORDER BY mac, last_seen DESC` rows, err := r.db.ExecuteQuery(ctx, query, macs) if err != nil { return nil, err } result := make(map[string]string, len(rows)) for _, row := range rows { rawMAC, _ := row["mac"].(string) deviceID, _ := row["device_id"].(string) deviceID = strings.TrimSpace(deviceID) if rawMAC == "" || deviceID == "" || isLegacyIPBasedID(deviceID) { continue ... (clipped 61 lines) ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>🟡</td> <td> <details> <summary>🎫 <a href=https://github.com/carverauto/serviceradar/issues/2018>#2018</a></summary> <table width='100%'><tbody> <tr><td rowspan=7>🟢</td> <td>Replace KV-based identity publisher/resolver with a system that generates stable <br>ServiceRadar UUIDs via deterministic identity resolution prioritizing strong identifiers <br>(MAC, Armis ID, NetBox ID) and using IP as a weak fallback.</td></tr> <tr><td>Integrate a CNPG-based identity resolver that queries unified_devices to enrich updates <br>with canonical metadata and reduce write amplification.</td></tr> <tr><td>Integrate the new identity resolution into the registry/device processing pipeline, <br>including batch deduplication and metadata merging.</td></tr> <tr><td>Update sweep result processor and Armis sync to leave DeviceID empty so the registry <br>assigns stable ServiceRadar UUIDs.</td></tr> <tr><td>Add database methods to query stale devices and soft-delete orphaned records.</td></tr> <tr><td>Provide comprehensive tests covering identity resolution, canonicalization, and cleanup <br>flows.</td></tr> <tr><td>Update documentation/architecture findings and include a migration to merge duplicate <br>devices in unified_devices.</td></tr> <tr><td rowspan=1>⚪</td> <td>Add a StaleDeviceReaper to remove orphaned IP-only devices without strong identifiers on a <br>configurable interval.</td></tr> </tbody></table> </details> </td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=3>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=3>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR105-R177'><strong>Limited Auditing</strong></a>: New identity resolution generates and assigns canonical IDs and performs merges/lookups <br>without explicit audit logging of these critical actions, making full auditability <br>uncertain from the diff alone.<br> <details open><summary>Referred Code</summary> ```go func (r *DeviceIdentityResolver) ResolveDeviceID(ctx context.Context, update *models.DeviceUpdate) (string, error) { if r == nil || r.db == nil { // Fallback to existing behavior if resolver not configured return update.DeviceID, nil } // Skip service component IDs - they use a different identity scheme if isServiceDeviceID(update.DeviceID) { return update.DeviceID, nil } // Extract identifiers from update identifiers := r.extractIdentifiers(update) // Check cache first for strong identifiers if deviceID := r.checkCacheForStrongIdentifiers(identifiers); deviceID != "" { return deviceID, nil } // Prefer strong identifier matches even when IP differs if matches, err := r.findExistingDevicesByStrongIdentifiers(ctx, ... (clipped 52 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR138-R177'><strong>Swallowed Errors</strong></a>: Several database lookup errors are downgraded to debug logs and resolution proceeds (e.g., <br>generating new IDs) which may hide operational issues without clear propagation or <br>metrics.<br> <details open><summary>Referred Code</summary> ```go existingDeviceID, err := r.findExistingDevice(ctx, identifiers, true) if err != nil { r.logger.Debug().Err(err).Msg("Failed to find existing device, will generate new ID") } if existingDeviceID != "" { // Cache the mapping for future lookups r.cacheIdentifierMappings(identifiers, existingDeviceID) return existingDeviceID, nil } // Check if the update already has a ServiceRadar UUID if isServiceRadarUUID(update.DeviceID) { r.cacheIdentifierMappings(identifiers, update.DeviceID) return update.DeviceID, nil } // Check cache for weak identifier (IP) // We check this even if strong identifiers are present, because we want to // reuse the existing UUID for this IP if one exists. for _, ip := range identifiers.IPs { ... (clipped 19 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/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR320-R373'><strong>Input Trust</strong></a>: The resolver trusts metadata fields like &#x27;armis_device_id&#x27; and <br>&#x27;integration_id&#x27; from updates without visible validation or normalization beyond <br>trimming, which may require upstream validation guarantees.<br> <details open><summary>Referred Code</summary> ```go func (r *DeviceIdentityResolver) extractIdentifiers(update *models.DeviceUpdate) *deviceIdentifiers { ids := &deviceIdentifiers{ IP: strings.TrimSpace(update.IP), ExistingID: strings.TrimSpace(update.DeviceID), } if update.MAC != nil { ids.MAC = normalizeMAC(*update.MAC) } if update.Metadata != nil { if armisID := strings.TrimSpace(update.Metadata["armis_device_id"]); armisID != "" { ids.ArmisID = armisID } if netboxID := strings.TrimSpace(update.Metadata["netbox_device_id"]); netboxID != "" { ids.NetboxID = netboxID } // Also check integration_id for netbox type if update.Metadata["integration_type"] == "netbox" { if intID := strings.TrimSpace(update.Metadata["integration_id"]); intID != "" { ids.NetboxID = intID ... (clipped 33 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 2025-11-26 00:41:11 +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/2019#issuecomment-3578231386
Original created: 2025-11-26T00:41:11Z

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
UUID generation relies on ephemeral IPs

The deterministic UUID generation relies on the device's IP address, which is
ephemeral in DHCP environments, leading to device duplication when IPs change.
The suggestion is to base the UUID on a stable, strong identifier (like a MAC
address) when available, and only use the IP as a fallback.

Examples:

pkg/registry/device_identity.go [846-882]
func generateServiceRadarDeviceID(update *models.DeviceUpdate) string {
	// Create a deterministic seed from available identifiers
	h := sha256.New()
	h.Write([]byte("serviceradar-device-v2:"))

	// Primary identity anchor
	ip := strings.TrimSpace(update.IP)
	partition := strings.TrimSpace(update.Partition)
	if partition == "" {
		partition = "default"

 ... (clipped 27 lines)

Solution Walkthrough:

Before:

func generateServiceRadarDeviceID(update *models.DeviceUpdate) string {
    h := sha256.New()
    h.Write([]byte("serviceradar-device-v2:"))

    // UUID is ALWAYS based on IP + partition
    // This ensures determinism regardless of discovery order
    ip := strings.TrimSpace(update.IP)
    partition := strings.TrimSpace(update.Partition)
    h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip)))

    // ... generate UUID from hash
    return "sr:" + uuid
}

After:

func generateServiceRadarDeviceID(update *models.DeviceUpdate) string {
    h := sha256.New()
    h.Write([]byte("serviceradar-device-v3:"))

    // Prioritize strong identifiers for the UUID seed
    if mac := getStrongestIdentifier(update); mac != "" {
        h.Write([]byte("mac:" + mac))
    } else {
        // Fallback to IP + partition only if no strong identifiers are present
        ip := strings.TrimSpace(update.IP)
        partition := strings.TrimSpace(update.Partition)
        h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip)))
    }

    // ... generate UUID from hash
    return "sr:" + uuid
}

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical design flaw where basing UUIDs on ephemeral IPs can lead to device duplication during DHCP churn, which is a primary problem this PR aims to solve.

High
Possible issue
Fix race condition in cache

Fix a race condition in getIPMapping by re-checking the entry's state under a
write lock before deleting it. This ensures the check-and-delete operation for
expired entries is atomic.

pkg/registry/identity_resolver_cnpg.go [70-90]

 func (c *identityResolverCache) getIPMapping(ip string) (string, bool) {
 	c.mu.RLock()
 	item, ok := c.ipToDeviceID[ip]
 	c.mu.RUnlock()
 
 	if !ok {
 		return "", false
 	}
 
 	if time.Now().After(item.expiresAt) {
+		// Re-check under a write lock to ensure atomicity of check-and-delete
 		c.mu.Lock()
-		delete(c.ipToDeviceID, ip)
+		// Verify the entry wasn't changed by another goroutine
+		item, ok = c.ipToDeviceID[ip]
+		if ok && time.Now().After(item.expiresAt) {
+			delete(c.ipToDeviceID, ip)
+		}
 		c.mu.Unlock()
 		return "", false
 	}
 
 	if deviceID, ok := item.value.(string); ok {
 		return deviceID, true
 	}
 	return "", false
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a classic check-then-act race condition when deleting an expired cache entry. The proposed fix using a double-checked locking pattern correctly resolves this concurrency issue, which is critical for the stability of a concurrent cache.

Medium
Prevent loss of resolved device IDs

Improve deduplicateBatch to prioritize keeping the canonical ServiceRadar UUID
(sr:...) when merging duplicate updates for the same IP. This prevents losing a
resolved DeviceID if it appears later in the batch.

pkg/registry/registry.go [284-312]

 func (r *DeviceRegistry) deduplicateBatch(updates []*models.DeviceUpdate) []*models.DeviceUpdate {
 	if len(updates) <= 1 {
 		return updates
 	}
 
 	// Track IPs seen in this batch
 	seenIPs := make(map[string]*models.DeviceUpdate)
 	result := make([]*models.DeviceUpdate, 0, len(updates))
 
 	for _, update := range updates {
 		// Skip updates without IP or with service component IDs
 		if update.IP == "" || isServiceDeviceID(update.DeviceID) {
 			result = append(result, update)
 			continue
 		}
 
 		if existing, ok := seenIPs[update.IP]; ok {
 			// Same IP twice in batch - merge into first occurrence
 			r.mergeUpdateMetadata(existing, update)
+			// If the new update has a canonical ID and the existing one doesn't,
+			// prefer the canonical ID to avoid losing the resolved identity.
+			if isServiceRadarUUID(update.DeviceID) && !isServiceRadarUUID(existing.DeviceID) {
+				existing.DeviceID = update.DeviceID
+			}
 		} else {
 			seenIPs[update.IP] = update
 			result = append(result, update)
 		}
 	}
 
 	return result
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out a flaw where the deduplicateBatch function might discard a more accurate, resolved DeviceID in favor of an unresolved one. The proposed change to prioritize canonical sr: UUIDs prevents this potential data loss and is a valuable improvement.

Medium
Filter out NULL IP addresses

Add a filter to the duplicates temporary table creation to exclude records with
NULL or empty ip addresses, preventing them from being incorrectly grouped and
merged.

pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql [6-39]

 CREATE TEMP TABLE duplicates AS WITH ranked AS (
     SELECT ip,
         device_id,
         metadata,
         mac,
         last_seen,
         ROW_NUMBER() OVER (
             PARTITION BY ip
             ORDER BY -- Prefer devices with Armis ID
                 CASE
                     WHEN metadata->>'armis_device_id' IS NOT NULL THEN 1
                     ELSE 0
                 END DESC,
                 ...
         ) as rank
     FROM unified_devices
     WHERE device_id LIKE 'sr:%'
+        AND ip IS NOT NULL
+        AND ip <> ''
         AND (
             metadata->>'_merged_into' IS NULL
             OR metadata->>'_merged_into' = ''
         )
         AND COALESCE(lower(metadata->>'_deleted'), 'false') <> 'true'
 )

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: This is a valid improvement that makes the migration script more robust by explicitly excluding records with NULL or empty ip addresses, preventing them from being incorrectly grouped and merged.

Medium
Ensure consistent IP filtering

Add a filter to the canonical_map temporary table creation to exclude records
with NULL or empty ip addresses, ensuring consistency with the duplicates table
for correct joins.

pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql [44-70]

 CREATE TEMP TABLE canonical_map AS WITH ranked AS (
     SELECT ip,
         device_id as canonical_id,
         ROW_NUMBER() OVER (
             PARTITION BY ip
             ORDER BY CASE
                     WHEN metadata->>'armis_device_id' IS NOT NULL THEN 1
                     ELSE 0
                 END DESC,
                 ...
         ) as rank
     FROM unified_devices
     WHERE device_id LIKE 'sr:%'
+        AND ip IS NOT NULL
+        AND ip <> ''
         AND (
             metadata->>'_merged_into' IS NULL
             OR metadata->>'_merged_into' = ''
         )
         AND COALESCE(lower(metadata->>'_deleted'), 'false') <> 'true'
 )

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: This suggestion correctly points out the need for consistent filtering between the duplicates and canonical_map tables to ensure the JOIN operation works as expected, improving the migration's correctness.

Medium
General
Improve cache eviction logic

Improve the evictOldest function to evict the oldest (soonest to expire) entries
instead of random ones after clearing expired items. This makes the cache
eviction policy more effective and aligns with the function's name.

pkg/registry/device_identity.go [815-838]

 func (c *deviceIdentityCache) evictOldest(m map[string]deviceIdentityCacheEntry, count int) {
 	now := time.Now()
 	evicted := 0
 
 	// Evict expired entries first
 	for key, entry := range m {
 		if evicted >= count {
 			break
 		}
 		if now.After(entry.expiresAt) {
 			delete(m, key)
 			evicted++
 		}
 	}
 
-	// Evict random entries if needed
-	for key := range m {
+	if evicted >= count {
+		return
+	}
+
+	// If still not enough evicted, find the oldest (least recently accessed) entries
+	type entryInfo struct {
+		key       string
+		expiresAt time.Time
+	}
+	entries := make([]entryInfo, 0, len(m))
+	for k, v := range m {
+		entries = append(entries, entryInfo{key: k, expiresAt: v.expiresAt})
+	}
+
+	// Sort by expiration time (ascending) to find the oldest
+	sort.Slice(entries, func(i, j int) bool {
+		return entries[i].expiresAt.Before(entries[j].expiresAt)
+	})
+
+	// Evict the oldest entries
+	for _, entry := range entries {
 		if evicted >= count {
 			break
 		}
-		delete(m, key)
+		delete(m, entry.key)
 		evicted++
 	}
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that the evictOldest function performs random eviction after removing expired items, which is suboptimal and contradicts the function's name. The proposed change to evict the truly oldest items is a significant improvement in the cache eviction logic.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2019#issuecomment-3578231386 Original created: 2025-11-26T00:41:11Z --- _You are nearing your monthly Qodo Merge usage quota. For more information, please visit [here](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/#cloud-users)._ ## PR Code Suggestions ✨ <!-- 34d5dc2 --> 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>UUID generation relies on ephemeral IPs</summary> ___ **The deterministic UUID generation relies on the device's IP address, which is <br>ephemeral in DHCP environments, leading to device duplication when IPs change. <br>The suggestion is to base the UUID on a stable, strong identifier (like a MAC <br>address) when available, and only use the IP as a fallback.** ### Examples: <details> <summary> <a href="https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR846-R882">pkg/registry/device_identity.go [846-882]</a> </summary> ```go func generateServiceRadarDeviceID(update *models.DeviceUpdate) string { // Create a deterministic seed from available identifiers h := sha256.New() h.Write([]byte("serviceradar-device-v2:")) // Primary identity anchor ip := strings.TrimSpace(update.IP) partition := strings.TrimSpace(update.Partition) if partition == "" { partition = "default" ... (clipped 27 lines) ``` </details> ### Solution Walkthrough: #### Before: ```go func generateServiceRadarDeviceID(update *models.DeviceUpdate) string { h := sha256.New() h.Write([]byte("serviceradar-device-v2:")) // UUID is ALWAYS based on IP + partition // This ensures determinism regardless of discovery order ip := strings.TrimSpace(update.IP) partition := strings.TrimSpace(update.Partition) h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip))) // ... generate UUID from hash return "sr:" + uuid } ``` #### After: ```go func generateServiceRadarDeviceID(update *models.DeviceUpdate) string { h := sha256.New() h.Write([]byte("serviceradar-device-v3:")) // Prioritize strong identifiers for the UUID seed if mac := getStrongestIdentifier(update); mac != "" { h.Write([]byte("mac:" + mac)) } else { // Fallback to IP + partition only if no strong identifiers are present ip := strings.TrimSpace(update.IP) partition := strings.TrimSpace(update.Partition) h.Write([]byte(fmt.Sprintf("partition:%s:ip:%s", partition, ip))) } // ... generate UUID from hash return "sr:" + uuid } ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical design flaw where basing UUIDs on ephemeral IPs can lead to device duplication during DHCP churn, which is a primary problem this PR aims to solve. </details></details></td><td align=center>High </td></tr><tr><td rowspan=4>Possible issue</td> <td> <details><summary>Fix race condition in cache<!-- not_implemented --></summary> ___ **Fix a race condition in <code>getIPMapping</code> by re-checking the entry's state under a <br>write lock before deleting it. This ensures the check-and-delete operation for <br>expired entries is atomic.** [pkg/registry/identity_resolver_cnpg.go [70-90]](https://github.com/carverauto/serviceradar/pull/2019/files#diff-e758ca8be15d52379ccb94d2b35754f96d2d4d6e80a0f7d0b4a9035c9572518fR70-R90) ```diff func (c *identityResolverCache) getIPMapping(ip string) (string, bool) { c.mu.RLock() item, ok := c.ipToDeviceID[ip] c.mu.RUnlock() if !ok { return "", false } if time.Now().After(item.expiresAt) { + // Re-check under a write lock to ensure atomicity of check-and-delete c.mu.Lock() - delete(c.ipToDeviceID, ip) + // Verify the entry wasn't changed by another goroutine + item, ok = c.ipToDeviceID[ip] + if ok && time.Now().After(item.expiresAt) { + delete(c.ipToDeviceID, ip) + } c.mu.Unlock() return "", false } if deviceID, ok := item.value.(string); ok { return deviceID, true } return "", false } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=1 --> <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a classic check-then-act race condition when deleting an expired cache entry. The proposed fix using a double-checked locking pattern correctly resolves this concurrency issue, which is critical for the stability of a concurrent cache. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Prevent loss of resolved device IDs</summary> ___ **Improve <code>deduplicateBatch</code> to prioritize keeping the canonical ServiceRadar UUID <br>(<code>sr:...</code>) when merging duplicate updates for the same IP. This prevents losing a <br>resolved <code>DeviceID</code> if it appears later in the batch.** [pkg/registry/registry.go [284-312]](https://github.com/carverauto/serviceradar/pull/2019/files#diff-cb61d8f79451b9541de4a8cc0811523a68d15452b2f5971c7618ea5b423cf4ecR284-R312) ```diff func (r *DeviceRegistry) deduplicateBatch(updates []*models.DeviceUpdate) []*models.DeviceUpdate { if len(updates) <= 1 { return updates } // Track IPs seen in this batch seenIPs := make(map[string]*models.DeviceUpdate) result := make([]*models.DeviceUpdate, 0, len(updates)) for _, update := range updates { // Skip updates without IP or with service component IDs if update.IP == "" || isServiceDeviceID(update.DeviceID) { result = append(result, update) continue } if existing, ok := seenIPs[update.IP]; ok { // Same IP twice in batch - merge into first occurrence r.mergeUpdateMetadata(existing, update) + // If the new update has a canonical ID and the existing one doesn't, + // prefer the canonical ID to avoid losing the resolved identity. + if isServiceRadarUUID(update.DeviceID) && !isServiceRadarUUID(existing.DeviceID) { + existing.DeviceID = update.DeviceID + } } else { seenIPs[update.IP] = update result = append(result, update) } } return result } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: The suggestion correctly points out a flaw where the `deduplicateBatch` function might discard a more accurate, resolved `DeviceID` in favor of an unresolved one. The proposed change to prioritize canonical `sr:` UUIDs prevents this potential data loss and is a valuable improvement. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Filter out NULL IP addresses</summary> ___ **Add a filter to the <code>duplicates</code> temporary table creation to exclude records with <br><code>NULL</code> or empty <code>ip</code> addresses, preventing them from being incorrectly grouped and <br>merged.** [pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql [6-39]](https://github.com/carverauto/serviceradar/pull/2019/files#diff-4dfc8968de91ccfe580087aea2962c4d3563814649f414623f148bebeec6a990R6-R39) ```diff CREATE TEMP TABLE duplicates AS WITH ranked AS ( SELECT ip, device_id, metadata, mac, last_seen, ROW_NUMBER() OVER ( PARTITION BY ip ORDER BY -- Prefer devices with Armis ID CASE WHEN metadata->>'armis_device_id' IS NOT NULL THEN 1 ELSE 0 END DESC, ... ) as rank FROM unified_devices WHERE device_id LIKE 'sr:%' + AND ip IS NOT NULL + AND ip <> '' AND ( metadata->>'_merged_into' IS NULL OR metadata->>'_merged_into' = '' ) AND COALESCE(lower(metadata->>'_deleted'), 'false') <> 'true' ) ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: This is a valid improvement that makes the migration script more robust by explicitly excluding records with `NULL` or empty `ip` addresses, preventing them from being incorrectly grouped and merged. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Ensure consistent IP filtering</summary> ___ **Add a filter to the <code>canonical_map</code> temporary table creation to exclude records <br>with <code>NULL</code> or empty <code>ip</code> addresses, ensuring consistency with the <code>duplicates</code> table <br>for correct joins.** [pkg/db/cnpg/migrations/00000000000007_merge_duplicates.up.sql [44-70]](https://github.com/carverauto/serviceradar/pull/2019/files#diff-4dfc8968de91ccfe580087aea2962c4d3563814649f414623f148bebeec6a990R44-R70) ```diff CREATE TEMP TABLE canonical_map AS WITH ranked AS ( SELECT ip, device_id as canonical_id, ROW_NUMBER() OVER ( PARTITION BY ip ORDER BY CASE WHEN metadata->>'armis_device_id' IS NOT NULL THEN 1 ELSE 0 END DESC, ... ) as rank FROM unified_devices WHERE device_id LIKE 'sr:%' + AND ip IS NOT NULL + AND ip <> '' AND ( metadata->>'_merged_into' IS NULL OR metadata->>'_merged_into' = '' ) AND COALESCE(lower(metadata->>'_deleted'), 'false') <> 'true' ) ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 7</summary> __ Why: This suggestion correctly points out the need for consistent filtering between the `duplicates` and `canonical_map` tables to ensure the `JOIN` operation works as expected, improving the migration's correctness. </details></details></td><td align=center>Medium </td></tr><tr><td rowspan=1>General</td> <td> <details><summary>Improve cache eviction logic<!-- not_implemented --></summary> ___ **Improve the <code>evictOldest</code> function to evict the oldest (soonest to expire) entries <br>instead of random ones after clearing expired items. This makes the cache <br>eviction policy more effective and aligns with the function's name.** [pkg/registry/device_identity.go [815-838]](https://github.com/carverauto/serviceradar/pull/2019/files#diff-93615f2e83c891a2500d87cffd7cab7ad255b47f7621ecee79b753d84ab1af4eR815-R838) ```diff func (c *deviceIdentityCache) evictOldest(m map[string]deviceIdentityCacheEntry, count int) { now := time.Now() evicted := 0 // Evict expired entries first for key, entry := range m { if evicted >= count { break } if now.After(entry.expiresAt) { delete(m, key) evicted++ } } - // Evict random entries if needed - for key := range m { + if evicted >= count { + return + } + + // If still not enough evicted, find the oldest (least recently accessed) entries + type entryInfo struct { + key string + expiresAt time.Time + } + entries := make([]entryInfo, 0, len(m)) + for k, v := range m { + entries = append(entries, entryInfo{key: k, expiresAt: v.expiresAt}) + } + + // Sort by expiration time (ascending) to find the oldest + sort.Slice(entries, func(i, j int) bool { + return entries[i].expiresAt.Before(entries[j].expiresAt) + }) + + // Evict the oldest entries + for _, entry := range entries { if evicted >= count { break } - delete(m, key) + delete(m, entry.key) evicted++ } } ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=5 --> <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies that the `evictOldest` function performs random eviction after removing expired items, which is suboptimal and contradicts the function's name. The proposed change to evict the truly oldest items is a significant improvement in the cache eviction logic. </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!2479
No description provided.