Skip to main content

Database Schema (AI Agent-Oriented)

This document summarizes the current schema for AI-agent workflows, using Alembic migrations as source of truth in backend/alembic/versions/*.py.
  • Current migration head in this repo: fb51d610b306 (backend/alembic/versions/fb51d610b306_add_github_audit_tables.py)
  • Base migration: bdf40d064ed1 (backend/alembic/versions/bdf40d064ed1_initial_database_migration.py)

Practical Domain Map

For typical agent change work, these domains are most relevant:
  • Connectors and integration metadata: connectors*, available_*, customer_*_connectors*, customer_*integrations*, integration_*, network_connectors_*, custom_alert_creation_*, monitoring_alerts, sigma_queries, github_audit_*
  • Auth / users / roles: user, role, smtp, user_customer_access, user_tag_access, role_tag_access
  • Incidents (alerts/cases/tags/comments): all incident_management_* tables
  • Scheduler/job metadata: scheduled_job_metadata, schedulerjob, index_snapshot_schedules
  • Agent data store / artifacts / reports: agent_datastore, incident_management_case_datastore, incident_management_case_report_template_datastore, vulnerability_reports, sca_reports, agent_vulnerabilities

Critical relationship graph

Compact relationship views for incident workflows and access controls.
incident_management_alert
  id (PK)
    |
    | 1-to-many via incident_management_asset.alert_linked
    v
incident_management_asset
  alert_linked (FK -> incident_management_alert.id)
  alert_context_id (FK -> incident_management_alertcontext.id)
  index_name, index_id (origin pointer into SIEM index document)
incident_management_case                      incident_management_alert
  id (PK)                                     id (PK)
     \                                           /
      \                                         /
       +-- incident_management_casealertlink --+
             case_id  (FK -> incident_management_case.id)
             alert_id (FK -> incident_management_alert.id)
             PK(case_id, alert_id)

Tags (alert/tag join)

incident_management_alert                 incident_management_alerttag
  id (PK)                                 id (PK), tag
     \                                      /
      \                                    /
       +-- incident_management_alert_to_tag --+
             alert_id (FK -> incident_management_alert.id)
             tag_id   (FK -> incident_management_alerttag.id)
             PK(alert_id, tag_id)

IoCs (alert/ioc join)

incident_management_alert                  incident_management_ioc
  id (PK)                                  id (PK), value/type/description
     \                                       /
      \                                     /
       +-- incident_management_alert_to_ioc --+
             alert_id (FK -> incident_management_alert.id)
             ioc_id   (FK -> incident_management_ioc.id)
             PK(alert_id, ioc_id)

Comments (alert comments + case comments)

incident_management_comment
  alert_id (FK -> incident_management_alert.id)
  comment, user_name, created_at

incident_management_case_comment
  case_id (FK -> incident_management_case.id)
  comment, user_name, created_at

Case datastore + report template datastore

incident_management_case
  id (PK)
    |
    | 1-to-many via incident_management_case_datastore.case_id
    v
incident_management_case_datastore
  case_id (FK -> incident_management_case.id)
  bucket_name, object_key, file_name, file_hash, upload_time

incident_management_case_report_template_datastore
  (global report templates; no case FK)
  report_template_name, bucket_name, object_key, file_name, file_hash, upload_time

Tag access control and alert visibility

incident_management_tag_access_settings
  enabled, untagged_alert_behavior, default_tag_id (FK -> incident_management_alerttag.id)

user_tag_access (user_id, tag_id)   role_tag_access (role_id, tag_id)
        \                                  /
         +------ allowed tag ids per identity ------+
                           |
incident_management_alert_to_tag (alert_id, tag_id)
                           |
                   incident_management_alert
When tag access control is enabled, alert visibility is constrained by the tag IDs reachable through user_tag_access and/or role_tag_access joined through incident_management_alert_to_tag. incident_management_tag_access_settings controls whether this filtering is active and what happens for untagged alerts (untagged_alert_behavior, optional default_tag_id fallback). Tag access enforcement location (code pointers)
  • Core tag RBAC logic: backend/app/incidents/middleware/tag_access.py (TagAccessHandler)
    • is_tag_rbac_enabled() (global enable/disable)
    • build_alert_query_filters() (computes accessible tags + untagged behavior)
    • check_alert_tag_access() / can_user_access_alert() (per-alert decision)
  • Applied in incident DB query layer:
    • backend/app/incidents/services/db_operations.py uses tag_access_handler.build_alert_query_filters() to add SQL exists() conditions when counting/listing alerts.

SIEM data origin + query pattern

  • Graylog alerting uses the gl-events index pattern (for example gl-events* in query flows).
  • Those Graylog alert documents live in Wazuh indexer storage (OpenSearch-backed).
  • Wazuh indexer is the backing SIEM event store across event sources (endpoints, O365 integrations, network connectors, and other ingested streams).
  • CoPilot commonly resolves and displays SIEM records by querying Wazuh indexer with index_name plus index_id.
  • Code pointers:
    • backend/app/connectors/wazuh_indexer/routes/alerts.py
    • backend/app/connectors/wazuh_indexer/services/alerts.py
    • backend/app/routers/wazuh_indexer.py
    • frontend/src/api/endpoints/alerts.ts

Table Inventory (Alembic-Derived)

Customer, Auth, and Core Platform

TablePKImportant columnsForeign keysModel file(s)
customersidcustomer_code, customer_name, contact/address fields, created_atNonebackend/app/db/universal_models.py (Customers)
customersmetaidcustomer_code, Graylog/Grafana/Wazuh metadata, customer_meta_portainer_stack_idcustomer_code -> customers.customer_codebackend/app/db/universal_models.py (CustomersMeta)
customer_provisioning_default_settingsidcluster_name, cluster_key, master_ip, grafana_url, wazuh_worker_hostnameNonebackend/app/customer_provisioning/models/default_settings.py
useridusername, password, email, created_at, role_idrole_id -> role.idbackend/app/auth/models/users.py (User)
roleidname, descriptionNonebackend/app/auth/models/users.py (Role)
smtpidemail, smtp_server, smtp_port, user_iduser_id -> user.idbackend/app/auth/models/users.py (SMTP)
user_customer_accessiduser_id, customer_code, created_atuser_id -> user.id, customer_code -> customers.customer_codebackend/app/auth/models/users.py
user_tag_accessiduser_id, tag_id, created_atuser_id -> user.id, tag_id -> incident_management_alerttag.idbackend/app/auth/models/users.py
role_tag_accessidrole_id, tag_id, created_atrole_id -> role.id, tag_id -> incident_management_alerttag.idbackend/app/auth/models/users.py
licenseidlicense_key, customer/company identity fieldsNonebackend/app/db/universal_models.py (License)
license_cacheidlicense_key, feature_name, is_enabled, cached_at, expires_at, license_dataNonebackend/app/db/universal_models.py (LicenseCache)
log_entriesidtimestamp, event_type, user_id, route, status_code, messageNonebackend/app/db/universal_models.py (LogEntry)
customer_portal_settingsidtitle, logo_base64, logo_mime_type, updated_at, updated_byNonebackend/app/db/universal_models.py (CustomerPortalSettings)

Agents, Vulnerability, and Artifact/Data Store

TablePKImportant columnsForeign keysModel file(s)
agentsidagent_id, host/OS/status fields, velociraptor_*, customer_code, quarantined, velociraptor_orgcustomer_code -> customers.customer_codebackend/app/db/universal_models.py (Agents)
agent_datastoreidagent_id, velociraptor_id, artifact_name, flow_id, storage columns (bucket_name,object_key,file_name), file_hash, statusagent_id -> agents.agent_idbackend/app/db/universal_models.py (AgentDataStore)
agent_vulnerabilitiesidcve_id, severity, title, status, discovered_at, agent_id, customer_codeagent_id -> agents.agent_id, customer_code -> customers.customer_codebackend/app/db/universal_models.py (AgentVulnerabilities)
vulnerability_reportsidreport_name, customer_code, storage columns, generated_at, vulnerability counters, statuscustomer_code -> customers.customer_codebackend/app/db/universal_models.py (VulnerabilityReport)
sca_reportsidreport_name, customer_code, storage columns, generated_at, SCA counters, statuscustomer_code -> customers.customer_codebackend/app/db/universal_models.py (SCAReport)

Scheduler and Job Metadata

TablePKImportant columnsForeign keysModel file(s)
scheduled_job_metadataidjob_id, last_success, time_interval, extra_data, enabled, job_descriptionNonebackend/app/schedulers/models/scheduler.py (JobMetadata)
schedulerjobidnext_run_time, job_stateNonebackend/app/db/universal_models.py (SchedulerJob)
index_snapshot_schedulesidschedule metadata (name, index_pattern, repository), retention/last execution fieldsNonebackend/app/connectors/wazuh_indexer/models/snapshot_and_restore.py

Connectors and Integrations

TablePKImportant columnsForeign keysModel file(s)
connectorsidconnector identity/endpoint/auth fields, capability flags, connector_enabledNonebackend/app/connectors/models.py (Connectors)
connectorhistoryidconnector_id, change_timestamp, change_descriptionconnector_id -> connectors.idbackend/app/connectors/models.py (ConnectorHistory)
available_integrationsidintegration_name, description, integration_detailsNonebackend/app/integrations/models/customer_integration_settings.py
available_integrations_auth_keysidintegration_id, integration_name, auth_key_nameintegration_id -> available_integrations.idbackend/app/integrations/models/customer_integration_settings.py
customer_integrationsidcustomer_code, integration_service_id, integration_service_name, deployedNonebackend/app/integrations/models/customer_integration_settings.py
integration_servicesidservice_name, auth_typeNonebackend/app/integrations/models/customer_integration_settings.py
integration_subscriptionsidcustomer_id, integration_service_idcustomer_id -> customer_integrations.id, integration_service_id -> integration_services.idbackend/app/integrations/models/customer_integration_settings.py
integration_configsidintegration_service_id, config_key, config_valueintegration_service_id -> integration_services.idbackend/app/integrations/models/customer_integration_settings.py
integration_auth_keysidsubscription_id, auth_key_name, auth_valuesubscription_id -> integration_subscriptions.idbackend/app/integrations/models/customer_integration_settings.py
customer_integrations_metaidGraylog/Grafana metadata (graylog_*, grafana_*, grafana_datasource_uid)Nonebackend/app/integrations/models/customer_integration_settings.py
available_network_connectorsidnetwork_connector_name, description, network_connector_detailsNonebackend/app/network_connectors/models/network_connectors.py
available_network_connectors_keysidnetwork_connector_id, network_connector_name, auth_key_namenetwork_connector_id -> available_network_connectors.idbackend/app/network_connectors/models/network_connectors.py
customer_network_connectorsidcustomer_code, network_connector_service_id, network_connector_service_name, deployedNonebackend/app/network_connectors/models/network_connectors.py
network_connectors_servicesidservice_name, auth_typeNonebackend/app/network_connectors/models/network_connectors.py
network_connectors_subscriptionsidcustomer_id, network_connectors_service_idcustomer_id -> customer_network_connectors.id, network_connectors_service_id -> network_connectors_services.idbackend/app/network_connectors/models/network_connectors.py
network_connectors_configsidnetwork_connector_service_id, config_key, config_valuenetwork_connector_service_id -> network_connectors_services.idbackend/app/network_connectors/models/network_connectors.py
network_connectors_keysidsubscription_id, auth_key_name, auth_valuesubscription_id -> network_connectors_subscriptions.idbackend/app/network_connectors/models/network_connectors.py
customer_network_connectors_metaidGraylog/Grafana connector metadata (graylog_*, grafana_*, grafana_datasource_uid)Nonebackend/app/network_connectors/models/network_connectors.py
custom_alert_creation_settingsidcustomer-wide alert-creation settings, nvd_url, custom integration URLsNonebackend/app/integrations/alert_creation_settings/models/alert_creation_settings.py
custom_alert_creation_event_orderidalert_creation_settings_id, order_labelalert_creation_settings_id -> custom_alert_creation_settings.idbackend/app/integrations/alert_creation_settings/models/alert_creation_settings.py
custom_alert_creation_conditionidevent_order_id, field_name, field_valueevent_order_id -> custom_alert_creation_event_order.idbackend/app/integrations/alert_creation_settings/models/alert_creation_settings.py
custom_alert_creation_event_configidevent_order_id, event_id, field, valueevent_order_id -> custom_alert_creation_event_order.idbackend/app/integrations/alert_creation_settings/models/alert_creation_settings.py
monitoring_alertsidalert_id, alert_index, customer_code, alert_sourceNonebackend/app/integrations/monitoring_alert/models/monitoring_alert.py
sigma_queriesidrule_name, rule_query, active, time_interval, execution timestampsNonebackend/app/connectors/wazuh_indexer/models/sigma.py

Incident Management (Alerts, Cases, Tags, Comments)

TablePKImportant columnsForeign keysModel file(s)
incident_management_alertidalert_name, alert_description, status, alert_creation_time, customer_code, source, assigned_to, escalatedNonebackend/app/incidents/models.py (Alert)
incident_management_alertcontextidsource, context (JSON)Nonebackend/app/incidents/models.py (AlertContext)
incident_management_assetidalert_linked, asset_name, alert_context_id, agent_id, customer_code, index_name, index_idalert_linked -> incident_management_alert.id, alert_context_id -> incident_management_alertcontext.idbackend/app/incidents/models.py (Asset)
incident_management_commentidalert_id, comment, user_name, created_atalert_id -> incident_management_alert.idbackend/app/incidents/models.py (Comment)
incident_management_caseidcase_name, case_description, case_creation_time, case_status, case_closed_time, assigned_to, customer_code, notification_invoked_number, escalatedNonebackend/app/incidents/models.py (Case)
incident_management_casealertlinkcomposite: (case_id, alert_id)link table case↔alertcase_id -> incident_management_case.id, alert_id -> incident_management_alert.idbackend/app/incidents/models.py (CaseAlertLink)
incident_management_case_commentidcase_id, comment, user_name, created_atcase_id -> incident_management_case.idbackend/app/incidents/models.py (CaseComment)
incident_management_alerttagidtagNonebackend/app/incidents/models.py (AlertTag)
incident_management_alert_to_tagcomposite: (alert_id, tag_id)link table alert↔tagalert_id -> incident_management_alert.id, tag_id -> incident_management_alerttag.idbackend/app/incidents/models.py (AlertToTag)
incident_management_iocidvalue, type, descriptionNonebackend/app/incidents/models.py (IoC)
incident_management_alert_to_ioccomposite: (alert_id, ioc_id)link table alert↔IoCalert_id -> incident_management_alert.id, ioc_id -> incident_management_ioc.idbackend/app/incidents/models.py (AlertToIoC)
incident_management_fieldnameidsource, field_nameNonebackend/app/incidents/models.py (FieldName)
incident_management_assetfieldnameidsource, field_nameNonebackend/app/incidents/models.py (AssetFieldName)
incident_management_timestampfieldnameidsource, field_nameNonebackend/app/incidents/models.py (TimestampFieldName)
incident_management_alerttitlefieldnameidsource, field_nameNonebackend/app/incidents/models.py (AlertTitleFieldName)
incident_management_iocfieldnameidsource, field_nameNonebackend/app/incidents/models.py (IoCFieldName)
incident_management_customercodefieldnameidsource, field_nameNonebackend/app/incidents/models.py (CustomerCodeFieldName)
incident_management_notificationidcustomer_code, shuffle_workflow_id, enabledNonebackend/app/incidents/models.py (Notification)
incident_management_case_datastoreidcase_id, storage fields (bucket_name,object_key,file_name), upload_time, file_hashcase_id -> incident_management_case.idbackend/app/incidents/models.py (CaseDataStore)
incident_management_case_report_template_datastoreidreport_template_name, storage fields, upload_time, file_hashNonebackend/app/incidents/models.py (CaseReportTemplateDataStore)
incident_management_tag_access_settingsidenabled, untagged_alert_behavior, default_tag_id, updated_at, updated_bydefault_tag_id -> incident_management_alerttag.idbackend/app/incidents/models.py (TagAccessSettings)
incident_management_velo_sigma_exclusionidname, field_matches (JSON), channel, title, customer_code, created_by, enabledNonebackend/app/incidents/models.py (VeloSigmaExclusion)

GitHub Audit (Added at Head)

TablePKImportant columnsForeign keysModel file(s)
github_audit_configidcustomer/org token/configuration, filters (JSON), notification and score threshold fieldsNonebackend/app/integrations/github_audit/model.py
github_audit_check_exclusionidconfig_id, customer_code, check/resource selectors, approval/expiry, enabledconfig_id -> github_audit_config.idbackend/app/integrations/github_audit/model.py
github_audit_reportidconfig_id, organization/report metadata, summary counts, status, report JSON blobsconfig_id -> github_audit_config.idbackend/app/integrations/github_audit/model.py
github_audit_baselineidconfig_id, customer_code, baseline definition, expected checks (JSON), baseline_report_id, is_activeconfig_id -> github_audit_config.id, baseline_report_id -> github_audit_report.idbackend/app/integrations/github_audit/model.py

Quick Model Scan: Tables Not Obvious From Alembic

The following SQLModel tables are defined in code but do not appear in backend/alembic/versions/*.py migrations. Treat them as drift candidates / runtime-created tables unless there is an out-of-band migration process.
Table (in SQLModel)Model file
sublimealertsbackend/app/connectors/sublime/models/alerts.py (SublimeAlerts)
flaggedrulebackend/app/connectors/sublime/models/alerts.py (FlaggedRule)
mailboxbackend/app/connectors/sublime/models/alerts.py (Mailbox)
triggeredactionbackend/app/connectors/sublime/models/alerts.py (TriggeredAction)
senderbackend/app/connectors/sublime/models/alerts.py (Sender)
recipientbackend/app/connectors/sublime/models/alerts.py (Recipient)
disabledrulebackend/app/connectors/wazuh_manager/models/rules.py (DisabledRule)
sap_siem_multiple_loginsbackend/app/integrations/sap_siem/models/sap_siem.py (SapSiemMultipleLogins)

Where To Change Schema

Source of truth locations

  • Alembic migrations: backend/alembic/versions/
  • Alembic env/config: backend/alembic/env.py
  • SQLModel definitions commonly touched:
    • backend/app/db/universal_models.py
    • backend/app/incidents/models.py
    • backend/app/auth/models/users.py
    • backend/app/network_connectors/models/network_connectors.py
    • backend/app/integrations/models/customer_integration_settings.py

Practical workflow

  1. Update or add SQLModel fields/classes in the relevant model file.
  2. Generate a migration under backend/alembic/versions/ (or author manually if needed).
  3. Review migration upgrade() and downgrade() carefully (FK names, nullable transitions, indexes).
  4. Apply migration locally and run tests.
  5. Update this document if table shape/ownership changes.

Notes for agent changes

  • Prefer extending existing domain tables over creating parallel tables when possible (especially incidents and connector metadata).
  • For incident workflows, changes usually involve: incident_management_alert, incident_management_case, link tables (*_to_*), and optional datastore tables.
  • For connector onboarding, changes usually involve: available_*, customer_*, *_services, *_subscriptions, *_configs, *_keys, and *_meta tables.
  • For scheduler automation, coordinate changes between scheduled_job_metadata, schedulerjob, and domain-specific tables storing job outcomes.