Skip to main content

Assets & CMDB

The assets system provides a universal identity layer that maps all infrastructure objects — hosts, clusters, databases, caches — to a single entity model. This enables unified inventory views, compliance scoping, and cross-asset operations.

Phase

CMDB Phase 1 complete (hosts + integrations). Phase 2 complete (K8s clusters + 13 resource types). Database CMDB Phase 2 complete (dedicated databases + database_clusters tables). The asset model now covers 7 asset types: host, cluster, database, database_cluster, cache, web_server, and container_runtime.

Overview

Every infrastructure object in Proxima Console gets an asset record in the assets table. The asset acts as a thin universal layer with shared attributes (ownership, status, tags), while type-specific data stays in dedicated tables (hosts, clusters, etc.) or is captured in the asset's metadata JSONB column (for integration-derived assets).

Asset Types

Asset TypeSourceref_id Points ToCreated By
hostAgent inventoryhosts.idInventory worker (EnsureForHost)
clusterK8s collectorclusters.idKubernetes worker (EnsureForCluster)
databaseAgent heartbeat (PostgreSQL collector)databases.idHeartbeat worker (syncDatabases)
database_clusterAuto-cluster detectiondatabase_clusters.idHeartbeat worker (detectDatabaseCluster)
cacheAgent heartbeat (Redis integration)host_integrations.idHeartbeat worker (EnsureForIntegration)
web_serverAgent heartbeat (Nginx integration)host_integrations.idHeartbeat worker (EnsureForIntegration)
container_runtimeAgent heartbeat (Docker integration)host_integrations.idHeartbeat worker (EnsureForIntegration)

Asset Lifecycle

Assets are created and updated automatically — no manual intervention needed.

How Assets Are Created

Host assets are created by the inventory worker every time it processes a host inventory message from the agent. This happens on agent startup and every collection cycle (default: 5 minutes).

The compliance worker also creates assets as a safety net, using EnsureForHostMinimal — this covers edge cases where a healthcheck runs before the first inventory arrives.

Cluster assets are created by the kubernetes worker when it processes K8s inventory messages from the cluster agent. See Kubernetes Inventory.

Integration-derived assets are created by the heartbeat worker via the integration-to-asset bridge (see below).

Integration-to-Asset Bridge

When a host agent sends a heartbeat containing integration collector statuses (PostgreSQL, Redis, Nginx, Docker), the heartbeat worker automatically creates or updates CMDB assets for each integration. This bridges per-host integration monitoring into the unified asset layer.

The flow:

  1. Agent heartbeat arrives with collectors[] array (type, name, version, status, enabled).
  2. Heartbeat worker upserts each collector into host_integrations.
  3. For each collector, worker looks up the host_integrations.id and calls EnsureForIntegration.
  4. The asset is created with ref_id = host_integrations.id, inheriting the host's tags and environment.

Integration-to-asset type mapping:

Integration IDAsset TypeDisplay Name
postgresqldatabasePostgreSQL
rediscacheRedis
nginxweb_serverNginx
dockercontainer_runtimeDocker

Unknown integration IDs fall through as-is (extensible fallback).

Asset naming: Integration assets are named "{DisplayName} on {hostname}" — e.g., "PostgreSQL on db-prod-01".

Metadata example for a database asset:

{
"engine": "postgresql",
"host_id": "a1b2c3d4-...",
"version": "16.2",
"collector_name": "postgresql-main"
}

Status Mapping

Asset status is derived from the source entity's lifecycle events.

Host assets:

EventAsset Status
Inventory received (agent online)active
Stale detector marks host offlineunreachable
Heartbeat arrives after being offlineactive (restored from unreachable; degraded/maintenance are not overridden)
Admin deactivates host via APIinactive
Admin activates host via APIactive
Host deleted via APIdecommissioned
Host row removed but asset orphaned (e.g. environment-cascade delete)decommissioned (reconciled by the stale detector)

Cluster assets:

Cluster StatusAsset Status
healthyactive
degradeddegraded
unreachableunreachable
unknownactive

Integration assets:

ConditionAsset Status
Integration enabled, collector healthyactive
Integration enabled, collector status = errordegraded
Integration disabledinactive

Additional statuses:

  • maintenance — manually set during planned downtime

Data Model

Asset Fields

FieldTypeDescription
idUUIDUnique asset identifier
asset_typestringType discriminator: host, cluster, database, database_cluster, cache, web_server, container_runtime
ref_idUUIDFK to the source entity (e.g., hosts.id, clusters.id, host_integrations.id)
client_idUUIDOwning client (multi-tenant scoping)
environment_idUUIDOptional environment scope
namestringHuman-readable display name (hostname for hosts, cluster name for clusters, "PostgreSQL on host" for integrations)
statusstringLifecycle status (see above)
tagsJSONBType-agnostic labels (synced from agent labels for hosts and integration assets)
metadataJSONBCustom fields and annotations (populated for integration assets with engine, version, host_id)
last_seen_attimestampLast data collection timestamp
created_attimestampWhen the asset was first seen
updated_attimestampLast modification timestamp

Host Linkage

Hosts have a reverse FK asset_id pointing back to their asset record. This is populated lazily by the inventory worker on first inventory.

-- Find the asset for a host
SELECT a.* FROM assets a WHERE a.asset_type = 'host' AND a.ref_id = :host_id;

-- Or via the reverse FK
SELECT a.* FROM assets a JOIN hosts h ON h.asset_id = a.id WHERE h.id = :host_id;

API Endpoints

All endpoints require authentication and the assets:read permission. Responses are scoped to the caller's allowed clients.

List Assets

GET /api/v1/assets

Query parameters:

ParameterTypeDescription
typestringFilter by asset type (e.g., host, cluster, database)
statusstringFilter by status (comma-separated: active,unreachable)
client_idUUIDFilter by client
environment_idUUIDFilter by environment
searchstringName search (case-insensitive substring match)
pageintPage number (default: 1)
per_pageintItems per page (default: 50)

Response:

{
"data": [
{
"id": "a1b2c3d4-...",
"asset_type": "host",
"ref_id": "e5f6g7h8-...",
"client_id": "c1d2e3f4-...",
"name": "web-prod-01",
"status": "active",
"tags": {"env": "production", "role": "web"},
"metadata": {},
"last_seen_at": "2026-04-05T12:00:00Z",
"created_at": "2026-03-01T00:00:00Z",
"updated_at": "2026-04-05T12:00:00Z"
}
],
"meta": {
"total": 42,
"page": 1,
"per_page": 50
}
}

Get Asset

GET /api/v1/assets/:id

Returns a single asset. Returns 404 if the asset doesn't exist or belongs to a client the caller can't access.

Asset Summary

GET /api/v1/assets/summary

Returns counts grouped by type and status:

{
"data": [
{"asset_type": "host", "status": "active", "count": 38},
{"asset_type": "host", "status": "unreachable", "count": 4},
{"asset_type": "cluster", "status": "active", "count": 3},
{"asset_type": "database", "status": "active", "count": 5},
{"asset_type": "database", "status": "degraded", "count": 1}
]
}

Client-Scoped Assets

GET /api/v1/clients/:id/assets

Same query parameters as the main list endpoint (minus client_id). Returns only assets belonging to the specified client.

Environment-Scoped Assets

GET /api/v1/environments/:envID/assets

Same query parameters as the main list endpoint (minus client_id and environment_id). Returns only assets in the specified environment.

The asset.* prefix is available in PQL for searching assets and hosts by their asset attributes:

QueryWhat it finds
asset.type = "host"All host assets
asset.type = "database"All database assets (PostgreSQL instances)
asset.type = "cache"All cache assets (Redis instances)
asset.type = "cluster"All cluster assets
asset.status = "active"Assets with active status
asset.status = "degraded"Assets with degraded status (integration errors, unhealthy clusters)
asset.name ~ "web-*"Assets with names starting with "web-"
asset.tags.env = "prod"Assets with env=prod tag

Combine with other PQL fields:

host.os = "Ubuntu" AND asset.status = "active" AND asset.tags.tier = "production"

See the PQL Reference for full syntax documentation.

Asset Cleanup and Staleness

Assets are not automatically deleted when their source entity disappears. Instead, status transitions track the lifecycle:

  • Hosts — The stale detector marks hosts as unreachable after a configurable timeout (default: 5 minutes). The asset status follows.
  • Clusters — The K8s maintenance worker marks clusters as disconnected when no inventory arrives for 15 minutes. The asset status is updated to unreachable.
  • Integration assets — Status is updated on every heartbeat. If the integration is disabled, the asset transitions to inactive. If the integration collector reports errors, the asset transitions to degraded.
  • Decommissioning — When a host is deleted via the API, the asset transitions to decommissioned and stays in the database for auditing.

Permissions

Two permissions control asset access:

PermissionGranted ToDescription
assets:readAll system rolesView assets and summaries
assets:writeSuper Admin, AdminCreate and modify assets

These permissions are added to system roles automatically via database migration. Custom roles can be configured to include either permission.

Database Schema

assets table

CREATE TABLE assets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
asset_type TEXT NOT NULL,
ref_id UUID NOT NULL,
client_id UUID NOT NULL REFERENCES clients(id),
environment_id UUID REFERENCES environments(id),
name TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'active',
tags JSONB NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
last_seen_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(asset_type, ref_id)
);

Key indexes: client_id, environment_id, (client_id, asset_type), status, GIN on tags, trigram GIN on name, last_seen_at.

Cluster Assets

Kubernetes clusters are the second asset type in the CMDB, added in CMDB-2a. Each cluster gets an assets record with asset_type = 'cluster' and ref_id pointing to clusters.id.

Cluster assets appear alongside host assets in the unified views:

  • GET /api/v1/assets?type=cluster — Filter the asset list to show only clusters
  • GET /api/v1/assets/summary — Summary now includes {"asset_type": "cluster", "status": "active", "count": N} rows

The cluster status is mapped to an asset status when the KubernetesWorker processes inventory messages:

Cluster StatusAsset Status
healthyactive
degradeddegraded
unreachableunreachable
unknownactive

See Kubernetes Inventory for full details on the cluster data model and API endpoints.

Database Assets

Database instances and database clusters are the latest asset types in the CMDB, added in Database CMDB Phase 2. Each PostgreSQL instance gets an assets record with asset_type = 'database' and ref_id pointing to databases.id. Logical groups (primary + replicas) get a database_cluster asset with ref_id pointing to database_clusters.id.

See Database Inventory for full details on topology detection, auto-cluster grouping, and API endpoints.