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.