2612 bugcore elx interface graph upsert failed #2848

Merged
mfreeman451 merged 2 commits from refs/pull/2848/head into staging 2026-02-04 19:41:34 +00:00
mfreeman451 commented 2026-02-04 19:39:35 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #2697
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/2697
Original created: 2026-02-04T19:39:35Z
Original updated: 2026-02-04T19:42:36Z
Original head: carverauto/serviceradar:2612-bugcore-elx-interface-graph-upsert-failed
Original base: staging
Original merged: 2026-02-04T19:41:34Z 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

Bug fix, Enhancement


Description

  • Standardize AGE graph name to platform_graph across core-elx and SRQL

  • Add configurable graph name with environment variable support

  • Grant USAGE/CREATE/ALL privileges on graph schema to application role

  • Transfer ownership of graph schema and label tables to application role

  • Create migration to ensure canonical graph exists at startup


Diagram Walkthrough

flowchart LR
  Config["Configuration<br/>age_graph_name"]
  CoreElx["Core-elx<br/>Graph Module"]
  SRQL["SRQL<br/>Query Engine"]
  Migration["Startup Migration<br/>Privileges & Ownership"]
  Database["PostgreSQL<br/>platform_graph Schema"]
  
  Config -->|default: platform_graph| CoreElx
  Config -->|default: platform_graph| SRQL
  CoreElx -->|uses configured name| Database
  SRQL -->|uses configured name| Database
  Migration -->|grants USAGE/CREATE/ALL| Database
  Migration -->|transfers ownership| Database

File Walkthrough

Relevant files
Configuration changes
3 files
config.exs
Add age_graph_name configuration default                                 
+3/-0     
runtime.exs
Load age_graph_name from environment variables                     
+7/-0     
runtime.exs
Load age_graph_name from environment variables                     
+7/-0     
Bug fix
3 files
startup_migrations.ex
Grant privileges and transfer ownership of graph schema   
+41/-3   
graph.ex
Use configurable graph name instead of hardcoded value     
+11/-6   
graph_cypher.rs
Pass graph_name parameter to SQL builder                                 
+14/-8   
Database migration
1 files
20260204090000_use_platform_age_graph.exs
Create migration to ensure platform_graph exists                 
+47/-0   
Tests
4 files
mapper_graph_ingestion_test.exs
Update test to use configurable graph name                             
+10/-6   
graph_cypher_integration_test.exs
Update test graph name to platform_graph                                 
+1/-1     
harness.rs
Update test config to use platform_graph                                 
+2/-1     
seed.sql
Update fixture to create platform_graph instead of serviceradar
+17/-17 
Enhancement
2 files
config.rs
Add age_graph_name field to AppConfig struct                         
+10/-0   
mod.rs
Pass age_graph_name to graph_cypher execute function         
+5/-2     
Documentation
7 files
age-graph-schema.md
Update documentation to reference platform_graph                 
+5/-5     
age-graph-readiness.md
Update runbook commands to use platform_graph                       
+20/-20 
design.md
Add design document for AGE graph schema fix                         
+38/-0   
proposal.md
Add proposal document for AGE graph schema fix                     
+15/-0   
spec.md
Add specification for canonical AGE graph schema access   
+15/-0   
tasks.md
Add implementation tasks for AGE graph schema fix               
+8/-0     
spec.md
Add canonical AGE graph schema access requirement               
+15/-0   

Imported from GitHub pull request. Original GitHub pull request: #2697 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/2697 Original created: 2026-02-04T19:39:35Z Original updated: 2026-02-04T19:42:36Z Original head: carverauto/serviceradar:2612-bugcore-elx-interface-graph-upsert-failed Original base: staging Original merged: 2026-02-04T19:41:34Z 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** Bug fix, Enhancement ___ ### **Description** - Standardize AGE graph name to `platform_graph` across core-elx and SRQL - Add configurable graph name with environment variable support - Grant USAGE/CREATE/ALL privileges on graph schema to application role - Transfer ownership of graph schema and label tables to application role - Create migration to ensure canonical graph exists at startup ___ ### Diagram Walkthrough ```mermaid flowchart LR Config["Configuration<br/>age_graph_name"] CoreElx["Core-elx<br/>Graph Module"] SRQL["SRQL<br/>Query Engine"] Migration["Startup Migration<br/>Privileges & Ownership"] Database["PostgreSQL<br/>platform_graph Schema"] Config -->|default: platform_graph| CoreElx Config -->|default: platform_graph| SRQL CoreElx -->|uses configured name| Database SRQL -->|uses configured name| Database Migration -->|grants USAGE/CREATE/ALL| Database Migration -->|transfers ownership| Database ``` <details><summary><h3>File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Configuration changes</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>config.exs</strong><dd><code>Add age_graph_name configuration default</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-42b3888cc53d9dcf4ebc45ec7fffb2c672b129bffe763b6c76de58e4678a13a8">+3/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>runtime.exs</strong><dd><code>Load age_graph_name from environment variables</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-5d045e2af9f15590a9745058e25a00380ad7a2cc363d0a9934715bd11ea8eef3">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>runtime.exs</strong><dd><code>Load age_graph_name from environment variables</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-0d835809f7c8d80e73d1de1ced438bbe735892a0b4af1a2b650eff6ce1699d43">+7/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Bug fix</strong></td><td><details><summary>3 files</summary><table> <tr> <td><strong>startup_migrations.ex</strong><dd><code>Grant privileges and transfer ownership of graph schema</code>&nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-576a6d0bf0bf904dc22e581cac58d4da08016f26ba51837b964090cf2f661ddf">+41/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>graph.ex</strong><dd><code>Use configurable graph name instead of hardcoded value</code>&nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-dfb37c67d62e46e881a357342a8510b01c8a34411eea8447ed5954436b5cbd8a">+11/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>graph_cypher.rs</strong><dd><code>Pass graph_name parameter to SQL builder</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-378b0ec80e1b1221c347160a0dac36c82d082fa06766d47d18cf069220166309">+14/-8</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Database migration</strong></td><td><details><summary>1 files</summary><table> <tr> <td><strong>20260204090000_use_platform_age_graph.exs</strong><dd><code>Create migration to ensure platform_graph exists</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-4f0a152af38e85c43afd7c3c3fd00cb40cc0f24368cd495473fa185480a97a36">+47/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>4 files</summary><table> <tr> <td><strong>mapper_graph_ingestion_test.exs</strong><dd><code>Update test to use configurable graph name</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-db24a5e474945d7ff4ec349b295a9c5ad2cc0f1ccdcb085f2a40db4ba222b2d0">+10/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>graph_cypher_integration_test.exs</strong><dd><code>Update test graph name to platform_graph</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-62d708913a0c32472d10acb991c20e1513e159fc200bf9a7fb43f7c52d68bf02">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>harness.rs</strong><dd><code>Update test config to use platform_graph</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-f15ba86798901e9218b7310fbeae763be61ab6ccf3fe592a79d63eda02eea8b4">+2/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>seed.sql</strong><dd><code>Update fixture to create platform_graph instead of serviceradar</code></dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-1de433f1e0e2be718b04d148299c8f2c8c46a1f9c0b452f55bc1067e82b4edc1">+17/-17</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>config.rs</strong><dd><code>Add age_graph_name field to AppConfig struct</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-8a094b8b89c66adf329551634fa2c9c0e2ad9a1043f42012ad11cd3bf3f8ab6a">+10/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>mod.rs</strong><dd><code>Pass age_graph_name to graph_cypher execute function</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-393e3fa3d0e41741834cd7cd398a06111ab7b141ae6caca7a5dcc0e036172491">+5/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>7 files</summary><table> <tr> <td><strong>age-graph-schema.md</strong><dd><code>Update documentation to reference platform_graph</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-2dc9168ae343d7818dc9010b57aaeae3671e24e4a3d1fb640d907427e649a215">+5/-5</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>age-graph-readiness.md</strong><dd><code>Update runbook commands to use platform_graph</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-64fec6b9d2d878e0b9ffdf75868a063dd73d0980551622f567781240d41afbc2">+20/-20</a>&nbsp; </td> </tr> <tr> <td><strong>design.md</strong><dd><code>Add design document for AGE graph schema fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-06805c153db407e692aad50ffc2d4ec439c53c5268b977bcdb4ebaca1e8f5e95">+38/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong><dd><code>Add proposal document for AGE graph schema fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-a74c8eb104730bee8e1a874db55dbc377f3b826b655a27d94685f3e31d1716f4">+15/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong><dd><code>Add specification for canonical AGE graph schema access</code>&nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-ac1dac4965b2b116fe89ae5c59e5196bf825fc8bcd18ef639fda39a6a92f9686">+15/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong><dd><code>Add implementation tasks for AGE graph schema fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-1eefe325fd1d3291bd4b940ce0730238b5660d742213698d48a04ad53a9defa5">+8/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong><dd><code>Add canonical AGE graph schema access requirement</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td> <td><a href="https://github.com/carverauto/serviceradar/pull/2697/files#diff-662d053adc46ece4dbf533845381b1d39ea1dfbfbc0d1601dc1506785a768405">+15/-0</a>&nbsp; &nbsp; </td> </tr> </table></details></td></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2026-02-04 19:40:28 +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/2697#issuecomment-3849389185
Original created: 2026-02-04T19:40:28Z

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Config-driven privilege changes

Description: Startup migrations use an admin connection to grant privileges and change schema/table
ownership based on the configured :age_graph_name (SERVICERADAR_AGE_GRAPH_NAME /
AGE_GRAPH_NAME), so a compromised/misconfigured environment could steer privileged DDL
(GRANT/ALTER OWNER) toward an unintended schema and broaden access or alter ownership
beyond the intended AGE graph. startup_migrations.ex [323-411]

Referred Code
      # Also grant privileges on the AGE graph schema (configured graph name).
      # AGE creates a schema for each graph to store vertex/edge labels.
      graph_name = Application.get_env(:serviceradar_core, :age_graph_name, "platform_graph")
      ensure_age_graph_privileges!(conn, app_user, graph_name)
    end)
  end
end

# Grant privileges on an AGE graph schema using an admin connection.
# AGE creates a schema with the same name as the graph to store vertex/edge tables.
# The schema is owned by whoever ran create_graph(), which may be postgres superuser.
defp ensure_age_graph_privileges!(conn, app_user, graph_name) do
  # Check if schema exists using the admin connection
  case Postgrex.query!(conn, "SELECT 1 FROM pg_namespace WHERE nspname = $1", [graph_name]) do
    %{rows: []} ->
      Logger.debug("[StartupMigrations] AGE graph schema #{graph_name} does not exist; skipping privileges")

    _ ->
      Logger.info("[StartupMigrations] Granting privileges on AGE graph schema #{graph_name}")

      Postgrex.query!(


 ... (clipped 68 lines)
Ticket Compliance
🟡
🎫 #2612
🟢 Fix the core-elx “Interface graph upsert failed” error caused by PostgreSQL
insufficient_privilege / “permission denied for schema serviceradar” when executing AGE
cypher(...) upserts.
Confirm in a real environment that core-elx interface graph upserts no longer emit
permission denied for schema ... warnings (requires runtime DB role/privilege validation
and actual ingestion execution).
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: Security-First Input Validation and Data Handling

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

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:
Privilege change logging: The new privilege/ownership-changing operations are logged without actor/user context, so
it is unclear whether separate audit trail coverage exists for reconstructing who
initiated these critical permission changes.

Referred Code
      # Also grant privileges on the AGE graph schema (configured graph name).
      # AGE creates a schema for each graph to store vertex/edge labels.
      graph_name = Application.get_env(:serviceradar_core, :age_graph_name, "platform_graph")
      ensure_age_graph_privileges!(conn, app_user, graph_name)
    end)
  end
end

# Grant privileges on an AGE graph schema using an admin connection.
# AGE creates a schema with the same name as the graph to store vertex/edge tables.
# The schema is owned by whoever ran create_graph(), which may be postgres superuser.
defp ensure_age_graph_privileges!(conn, app_user, graph_name) do
  # Check if schema exists using the admin connection
  case Postgrex.query!(conn, "SELECT 1 FROM pg_namespace WHERE nspname = $1", [graph_name]) do
    %{rows: []} ->
      Logger.debug("[StartupMigrations] AGE graph schema #{graph_name} does not exist; skipping privileges")

    _ ->
      Logger.info("[StartupMigrations] Granting privileges on AGE graph schema #{graph_name}")

      Postgrex.query!(


 ... (clipped 67 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:
Migration fail-fast risk: The new ownership transfer logic uses Postgrex.query!/3 for multiple DDL statements
without any explicit handling for expected failure modes (e.g., insufficient privilege),
so startup may crash rather than degrade gracefully depending on deployment credentials.

Referred Code
defp ensure_age_graph_ownership!(conn, app_user, graph_name) do
  Logger.info("[StartupMigrations] Ensuring ownership for AGE graph schema #{graph_name}")

  Postgrex.query!(
    conn,
    "ALTER SCHEMA #{quote_ident(graph_name)} OWNER TO #{quote_ident(app_user)}",
    []
  )

  %{rows: rows} =
    Postgrex.query!(
      conn,
      "SELECT c.relkind, c.relname\n" <>
        "FROM pg_class c\n" <>
        "JOIN pg_namespace n ON n.oid = c.relnamespace\n" <>
        "WHERE n.nspname = $1\n" <>
        "AND c.relkind IN ('r', 'p', 'S')",
      [graph_name]
    )

  Enum.each(rows, fn [relkind, relname] ->


 ... (clipped 11 lines)

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

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/2697#issuecomment-3849389185 Original created: 2026-02-04T19:40:28Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/a27c45391263fb38014abd6117dec87f106592a7 --> 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>Config-driven privilege changes </strong></summary><br> <b>Description:</b> Startup migrations use an admin connection to grant privileges and change schema/table <br>ownership based on the configured <code>:age_graph_name</code> (<code>SERVICERADAR_AGE_GRAPH_NAME</code> / <br><code>AGE_GRAPH_NAME</code>), so a compromised/misconfigured environment could steer privileged DDL <br>(GRANT/ALTER OWNER) toward an unintended schema and broaden access or alter ownership <br>beyond the intended AGE graph. <strong><a href='https://github.com/carverauto/serviceradar/pull/2697/files#diff-576a6d0bf0bf904dc22e581cac58d4da08016f26ba51837b964090cf2f661ddfR323-R411'>startup_migrations.ex [323-411]</a></strong><br> <details open><summary>Referred Code</summary> ```elixir # Also grant privileges on the AGE graph schema (configured graph name). # AGE creates a schema for each graph to store vertex/edge labels. graph_name = Application.get_env(:serviceradar_core, :age_graph_name, "platform_graph") ensure_age_graph_privileges!(conn, app_user, graph_name) end) end end # Grant privileges on an AGE graph schema using an admin connection. # AGE creates a schema with the same name as the graph to store vertex/edge tables. # The schema is owned by whoever ran create_graph(), which may be postgres superuser. defp ensure_age_graph_privileges!(conn, app_user, graph_name) do # Check if schema exists using the admin connection case Postgrex.query!(conn, "SELECT 1 FROM pg_namespace WHERE nspname = $1", [graph_name]) do %{rows: []} -> Logger.debug("[StartupMigrations] AGE graph schema #{graph_name} does not exist; skipping privileges") _ -> Logger.info("[StartupMigrations] Granting privileges on AGE graph schema #{graph_name}") Postgrex.query!( ... (clipped 68 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/2612>#2612</a></summary> <table width='100%'><tbody> <tr><td rowspan=1>🟢</td> <td>Fix the <code>core-elx</code> “Interface graph upsert failed” error caused by PostgreSQL <br><code>insufficient_privilege</code> / “permission denied for schema serviceradar” when executing AGE <br><code>cypher(...)</code> upserts.</td></tr> <tr><td rowspan=1>⚪</td> <td>Confirm in a real environment that <code>core-elx</code> interface graph upserts no longer emit <br><code>permission denied for schema ...</code> warnings (requires runtime DB role/privilege validation <br>and actual ingestion execution).</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=4>🟢</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> <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:** 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: 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/2697/files#diff-576a6d0bf0bf904dc22e581cac58d4da08016f26ba51837b964090cf2f661ddfR323-R410'><strong>Privilege change logging</strong></a>: The new privilege/ownership-changing operations are logged without actor/user context, so <br>it is unclear whether separate audit trail coverage exists for reconstructing who <br>initiated these critical permission changes.<br> <details open><summary>Referred Code</summary> ```elixir # Also grant privileges on the AGE graph schema (configured graph name). # AGE creates a schema for each graph to store vertex/edge labels. graph_name = Application.get_env(:serviceradar_core, :age_graph_name, "platform_graph") ensure_age_graph_privileges!(conn, app_user, graph_name) end) end end # Grant privileges on an AGE graph schema using an admin connection. # AGE creates a schema with the same name as the graph to store vertex/edge tables. # The schema is owned by whoever ran create_graph(), which may be postgres superuser. defp ensure_age_graph_privileges!(conn, app_user, graph_name) do # Check if schema exists using the admin connection case Postgrex.query!(conn, "SELECT 1 FROM pg_namespace WHERE nspname = $1", [graph_name]) do %{rows: []} -> Logger.debug("[StartupMigrations] AGE graph schema #{graph_name} does not exist; skipping privileges") _ -> Logger.info("[StartupMigrations] Granting privileges on AGE graph schema #{graph_name}") Postgrex.query!( ... (clipped 67 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/2697/files#diff-576a6d0bf0bf904dc22e581cac58d4da08016f26ba51837b964090cf2f661ddfR379-R410'><strong>Migration fail-fast risk</strong></a>: The new ownership transfer logic uses <code>Postgrex.query!/3</code> for multiple DDL statements <br>without any explicit handling for expected failure modes (e.g., insufficient privilege), <br>so startup may crash rather than degrade gracefully depending on deployment credentials.<br> <details open><summary>Referred Code</summary> ```elixir defp ensure_age_graph_ownership!(conn, app_user, graph_name) do Logger.info("[StartupMigrations] Ensuring ownership for AGE graph schema #{graph_name}") Postgrex.query!( conn, "ALTER SCHEMA #{quote_ident(graph_name)} OWNER TO #{quote_ident(app_user)}", [] ) %{rows: rows} = Postgrex.query!( conn, "SELECT c.relkind, c.relname\n" <> "FROM pg_class c\n" <> "JOIN pg_namespace n ON n.oid = c.relnamespace\n" <> "WHERE n.nspname = $1\n" <> "AND c.relkind IN ('r', 'p', 'S')", [graph_name] ) Enum.each(rows, fn [relkind, relname] -> ... (clipped 11 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"> <!-- placeholder --> <!-- /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-02-04 19:40:40 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/2697#issuecomment-3849389974
Original created: 2026-02-04T19:40:40Z

CI Feedback 🧐

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

Action: build

Failed stage: Configure SRQL fixture database for tests []

Failed test name: ""

Failure summary:

The action failed during environment/secret validation because the required TLS CA certificate
secret SRQL_TEST_DATABASE_CA_CERT was not configured (it is empty in the job environment).
The
workflow explicitly checks for this secret and aborts with: SRQL_TEST_DATABASE_CA_CERT secret must
be configured to verify SRQL fixture TLS. which caused the step to exit with code 1.

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

139:  ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m
140:  ^[[36;1m  sudo apt-get update^[[0m
141:  ^[[36;1m  sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m
142:  ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m
143:  ^[[36;1m  sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
144:  ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m
145:  ^[[36;1m  sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
146:  ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m
147:  ^[[36;1m  sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m
148:  ^[[36;1melse^[[0m
149:  ^[[36;1m  echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m
150:  ^[[36;1m  exit 1^[[0m
151:  ^[[36;1mfi^[[0m
152:  ^[[36;1m^[[0m
153:  ^[[36;1mensure_pkg_config^[[0m
154:  ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m
155:  shell: /usr/bin/bash -e {0}
...

356:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
357:  env:
358:  BUILDBUDDY_ORG_API_KEY: ***
359:  SRQL_TEST_DATABASE_URL: ***
360:  SRQL_TEST_ADMIN_URL: ***
361:  SRQL_TEST_DATABASE_CA_CERT: 
362:  DOCKERHUB_USERNAME: ***
363:  DOCKERHUB_TOKEN: ***
364:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
365:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
366:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
367:  ##[endgroup]
368:  ##[group]Run : install rustup if needed
369:  ^[[36;1m: install rustup if needed^[[0m
370:  ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m
371:  ^[[36;1m  curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m
372:  ^[[36;1m  echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m
...

512:  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
513:  env:
514:  BUILDBUDDY_ORG_API_KEY: ***
515:  SRQL_TEST_DATABASE_URL: ***
516:  SRQL_TEST_ADMIN_URL: ***
517:  SRQL_TEST_DATABASE_CA_CERT: 
518:  DOCKERHUB_USERNAME: ***
519:  DOCKERHUB_TOKEN: ***
520:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
521:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
522:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
523:  CARGO_HOME: /home/runner/.cargo
524:  CARGO_INCREMENTAL: 0
525:  CARGO_TERM_COLOR: always
526:  ##[endgroup]
527:  ##[group]Run : work around spurious network errors in curl 8.0
528:  ^[[36;1m: work around spurious network errors in curl 8.0^[[0m
529:  ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m
...

580:  SRQL_TEST_DATABASE_CA_CERT: 
581:  DOCKERHUB_USERNAME: ***
582:  DOCKERHUB_TOKEN: ***
583:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
584:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
585:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
586:  CARGO_HOME: /home/runner/.cargo
587:  CARGO_INCREMENTAL: 0
588:  CARGO_TERM_COLOR: always
589:  ##[endgroup]
590:  Attempting to download 1.x...
591:  Acquiring v1.28.1 from https://github.com/bazelbuild/bazelisk/releases/download/v1.28.1/bazelisk-linux-amd64
592:  Adding to the cache ...
593:  Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.28.1/x64
594:  Added bazelisk to the path
595:  ##[warning]Failed to restore: Cache service responded with 400
596:  Restored bazelisk cache dir @ /home/runner/.cache/bazelisk
...

662:  env:
663:  BUILDBUDDY_ORG_API_KEY: ***
664:  SRQL_TEST_DATABASE_URL: ***
665:  SRQL_TEST_ADMIN_URL: ***
666:  SRQL_TEST_DATABASE_CA_CERT: 
667:  DOCKERHUB_USERNAME: ***
668:  DOCKERHUB_TOKEN: ***
669:  TEST_CNPG_DATABASE: serviceradar_web_ng_test
670:  INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp
671:  INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir
672:  CARGO_HOME: /home/runner/.cargo
673:  CARGO_INCREMENTAL: 0
674:  CARGO_TERM_COLOR: always
675:  ##[endgroup]
676:  SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS.
677:  ##[error]Process completed with exit code 1.
678:  Post job cleanup.

Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2697#issuecomment-3849389974 Original created: 2026-02-04T19:40:40Z --- ## CI Feedback 🧐 A test triggered by this PR failed. Here is an AI-generated analysis of the failure: <table><tr><td> **Action:** build</td></tr> <tr><td> **Failed stage:** [Configure SRQL fixture database for tests](https://github.com/carverauto/serviceradar/actions/runs/21685685174/job/62531942751) [❌] </td></tr> <tr><td> **Failed test name:** "" </td></tr> <tr><td> **Failure summary:** The action failed during environment/secret validation because the required TLS CA certificate <br>secret <code>SRQL_TEST_DATABASE_CA_CERT</code> was not configured (it is empty in the job environment).<br> The <br>workflow explicitly checks for this secret and aborts with: <code>SRQL_TEST_DATABASE_CA_CERT secret must </code><br><code>be configured to verify SRQL fixture TLS.</code> which caused the step to exit with code 1.<br> </td></tr> <tr><td> <details><summary>Relevant error logs:</summary> ```yaml 1: Runner name: 'arc-runner-set-hk6mk-runner-pf6zj' 2: Runner group name: 'Default' ... 139: ^[[36;1mif command -v apt-get >/dev/null 2>&1; then^[[0m 140: ^[[36;1m sudo apt-get update^[[0m 141: ^[[36;1m sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler cmake flex bison^[[0m 142: ^[[36;1melif command -v dnf >/dev/null 2>&1; then^[[0m 143: ^[[36;1m sudo dnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 144: ^[[36;1melif command -v yum >/dev/null 2>&1; then^[[0m 145: ^[[36;1m sudo yum install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 146: ^[[36;1melif command -v microdnf >/dev/null 2>&1; then^[[0m 147: ^[[36;1m sudo microdnf install -y gcc gcc-c++ make openssl-devel protobuf-compiler cmake flex bison^[[0m 148: ^[[36;1melse^[[0m 149: ^[[36;1m echo "Unsupported package manager; please install gcc, g++ (or clang), make, OpenSSL headers, pkg-config, and protoc manually." >&2^[[0m 150: ^[[36;1m exit 1^[[0m 151: ^[[36;1mfi^[[0m 152: ^[[36;1m^[[0m 153: ^[[36;1mensure_pkg_config^[[0m 154: ^[[36;1mprotoc --version || (echo "protoc installation failed" && exit 1)^[[0m 155: shell: /usr/bin/bash -e {0} ... 356: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 357: env: 358: BUILDBUDDY_ORG_API_KEY: *** 359: SRQL_TEST_DATABASE_URL: *** 360: SRQL_TEST_ADMIN_URL: *** 361: SRQL_TEST_DATABASE_CA_CERT: 362: DOCKERHUB_USERNAME: *** 363: DOCKERHUB_TOKEN: *** 364: TEST_CNPG_DATABASE: serviceradar_web_ng_test 365: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 366: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 367: ##[endgroup] 368: ##[group]Run : install rustup if needed 369: ^[[36;1m: install rustup if needed^[[0m 370: ^[[36;1mif ! command -v rustup &>/dev/null; then^[[0m 371: ^[[36;1m curl --proto '=https' --tlsv1.2 --retry 10 --retry-connrefused --location --silent --show-error --fail https://sh.rustup.rs | sh -s -- --default-toolchain none -y^[[0m 372: ^[[36;1m echo "$CARGO_HOME/bin" >> $GITHUB_PATH^[[0m ... 512: shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0} 513: env: 514: BUILDBUDDY_ORG_API_KEY: *** 515: SRQL_TEST_DATABASE_URL: *** 516: SRQL_TEST_ADMIN_URL: *** 517: SRQL_TEST_DATABASE_CA_CERT: 518: DOCKERHUB_USERNAME: *** 519: DOCKERHUB_TOKEN: *** 520: TEST_CNPG_DATABASE: serviceradar_web_ng_test 521: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 522: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 523: CARGO_HOME: /home/runner/.cargo 524: CARGO_INCREMENTAL: 0 525: CARGO_TERM_COLOR: always 526: ##[endgroup] 527: ##[group]Run : work around spurious network errors in curl 8.0 528: ^[[36;1m: work around spurious network errors in curl 8.0^[[0m 529: ^[[36;1m# https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/timeout.20investigation^[[0m ... 580: SRQL_TEST_DATABASE_CA_CERT: 581: DOCKERHUB_USERNAME: *** 582: DOCKERHUB_TOKEN: *** 583: TEST_CNPG_DATABASE: serviceradar_web_ng_test 584: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 585: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 586: CARGO_HOME: /home/runner/.cargo 587: CARGO_INCREMENTAL: 0 588: CARGO_TERM_COLOR: always 589: ##[endgroup] 590: Attempting to download 1.x... 591: Acquiring v1.28.1 from https://github.com/bazelbuild/bazelisk/releases/download/v1.28.1/bazelisk-linux-amd64 592: Adding to the cache ... 593: Successfully cached bazelisk to /home/runner/_work/_tool/bazelisk/1.28.1/x64 594: Added bazelisk to the path 595: ##[warning]Failed to restore: Cache service responded with 400 596: Restored bazelisk cache dir @ /home/runner/.cache/bazelisk ... 662: env: 663: BUILDBUDDY_ORG_API_KEY: *** 664: SRQL_TEST_DATABASE_URL: *** 665: SRQL_TEST_ADMIN_URL: *** 666: SRQL_TEST_DATABASE_CA_CERT: 667: DOCKERHUB_USERNAME: *** 668: DOCKERHUB_TOKEN: *** 669: TEST_CNPG_DATABASE: serviceradar_web_ng_test 670: INSTALL_DIR_FOR_OTP: /home/runner/_work/_temp/.setup-beam/otp 671: INSTALL_DIR_FOR_ELIXIR: /home/runner/_work/_temp/.setup-beam/elixir 672: CARGO_HOME: /home/runner/.cargo 673: CARGO_INCREMENTAL: 0 674: CARGO_TERM_COLOR: always 675: ##[endgroup] 676: SRQL_TEST_DATABASE_CA_CERT secret must be configured to verify SRQL fixture TLS. 677: ##[error]Process completed with exit code 1. 678: Post job cleanup. ``` </details></td></tr></table>
qodo-code-review[bot] commented 2026-02-04 19:42:36 +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/2697#issuecomment-3849398025
Original created: 2026-02-04T19:42:36Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Improve migration robustness for pre-existing schemas

In the database migration, change the duplicate_schema error from an exception
to a warning to make the migration more robust against pre-existing schemas.

elixir/serviceradar_core/priv/repo/migrations/20260204090000_use_platform_age_graph.exs [10-32]

 DO $$
 DECLARE
   graph_exists boolean;
   graph_name text := 'platform_graph';
 BEGIN
   SELECT EXISTS(SELECT 1 FROM ag_catalog.ag_graph WHERE name = graph_name) INTO graph_exists;
   IF NOT graph_exists THEN
     BEGIN
       PERFORM ag_catalog.create_graph(graph_name);
     EXCEPTION
       WHEN duplicate_schema THEN
-        RAISE EXCEPTION 'Schema % already exists; cannot create AGE graph. Drop or rename the schema before retrying.', graph_name;
+        RAISE WARNING 'Schema % already exists; cannot create AGE graph. This may be due to a previously failed migration. Proceeding, but graph may need manual cleanup.', graph_name;
       WHEN duplicate_object THEN
+        -- This can happen in a race condition if another process creates the graph
+        -- after our initial check. It's safe to ignore.
         NULL;
     END;
   END IF;
 
+  -- Re-check for existence to confirm creation or pre-existence.
   SELECT EXISTS(SELECT 1 FROM ag_catalog.ag_graph WHERE name = graph_name) INTO graph_exists;
   IF NOT graph_exists THEN
     RAISE EXCEPTION 'AGE graph "%" is missing after migration', graph_name;
   END IF;
 END
 $$;
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential failure case in the database migration and proposes a change to make it more robust, which is a valuable improvement for deployment stability.

Low
  • More
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/2697#issuecomment-3849398025 Original created: 2026-02-04T19:42:36Z --- ## PR Code Suggestions ✨ <!-- a27c453 --> 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>Possible issue</td> <td> <details><summary>Improve migration robustness for pre-existing schemas</summary> ___ **In the database migration, change the <code>duplicate_schema</code> error from an exception <br>to a warning to make the migration more robust against pre-existing schemas.** [elixir/serviceradar_core/priv/repo/migrations/20260204090000_use_platform_age_graph.exs [10-32]](https://github.com/carverauto/serviceradar/pull/2697/files#diff-4f0a152af38e85c43afd7c3c3fd00cb40cc0f24368cd495473fa185480a97a36R10-R32) ```diff DO $$ DECLARE graph_exists boolean; graph_name text := 'platform_graph'; BEGIN SELECT EXISTS(SELECT 1 FROM ag_catalog.ag_graph WHERE name = graph_name) INTO graph_exists; IF NOT graph_exists THEN BEGIN PERFORM ag_catalog.create_graph(graph_name); EXCEPTION WHEN duplicate_schema THEN - RAISE EXCEPTION 'Schema % already exists; cannot create AGE graph. Drop or rename the schema before retrying.', graph_name; + RAISE WARNING 'Schema % already exists; cannot create AGE graph. This may be due to a previously failed migration. Proceeding, but graph may need manual cleanup.', graph_name; WHEN duplicate_object THEN + -- This can happen in a race condition if another process creates the graph + -- after our initial check. It's safe to ignore. NULL; END; END IF; + -- Re-check for existence to confirm creation or pre-existence. SELECT EXISTS(SELECT 1 FROM ag_catalog.ag_graph WHERE name = graph_name) INTO graph_exists; IF NOT graph_exists THEN RAISE EXCEPTION 'AGE graph "%" is missing after migration', graph_name; END IF; END $$; ``` - [ ] **Apply / Chat** <!-- /improve --apply_suggestion=0 --> <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly identifies a potential failure case in the database migration and proposes a change to make it more robust, which is a valuable improvement for deployment stability. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] More <!-- /improve --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!2848
No description provided.