Asset Heartbeat Ingestion v1
Status
Implemented as a narrow ingestion foundation. Full asset discovery is not implemented.
Related Requirements
- SRS references: RMM asset inventory and unmanaged device visibility.
- Client response references: agents/connectors run inside tenant environments; active/passive discovery is Phase 1 but requires separate authorization contracts.
- ADR references: asset discovery contracts and platform stack ADR.
- Task board references: OP-026, OP-030, OP-011, OP-035.
Problem Statement
OneProtect needs the first real path from tenant-scoped asset signals into the canonical asset read model without jumping directly into scanners or agent runtime complexity.
Architectural Intent
Asset discovery remains event-driven. Heartbeat ingestion emits valid asset events, projections update tenant-scoped read models, and the UI/API show the resulting asset state.
What Was Implemented
- Minimal heartbeat ingestion source.
- Event-driven projection for asset and collector read models.
last_seenupdates for assets and collectors.- Deterministic merge behavior that preserves stronger identifiers.
- Safe IP/interface updates.
- Narrow unmanaged/unauthorized detection using existing alert/evidence/audit foundations.
- Asset APIs and lightweight Assets console rendering real ingested fields.
- Agent-enrolled assets are marked
managedthrough the verifiedagent_identitiesbinding even when there is no separateapproved_inventoryallowlist row. The allowlist remains the trust check for agentless/unknown discovery observations.
Components Involved
poc/ingest_api/asset_heartbeat_ingestion.pypoc/ingest_api/asset_read_model.pypoc/ingest_api/rule_evaluation.py- Asset storage migration under
db/postgres/ - Assets console page.
APIs / Events / Schemas
GET /api/v1/assetsGET /api/v1/assets/{asset_id}GET /api/v1/assets/{asset_id}/timelineGET /api/v1/collectorsGET /api/v1/commands- Events include
agent.enrolled,collector.enrolled,asset.discovered,asset.updated,asset.classified,asset.fingerprint.updated, andasset.telemetry.received.
Deployment Notes
This runs through the existing API, worker/event, and Postgres paths. It does not require osquery, Fleet, Nmap, SNMP, WMI, or NetFlow runtime.
Security / Tenant Isolation
- Asset tables are tenant-owned and RLS-protected.
- Projections use tenant-scoped event envelopes.
- Duplicate events remain idempotent.
- Unmanaged detection uses existing alert/evidence/audit path.
- Agent-backed heartbeat data must not be treated as an unauthorized discovery
solely because no
approved_inventoryrow exists; the enrolled or active agent identity is the tenant-scoped management proof.
Validation Steps
UI Validation
- Trigger asset ingestion using the smoke target.
- Open
/console/assets. - Confirm hostname, classification, lifecycle state, last seen, source confidence, collector, and source type are visible when available.
- Confirm enrolled/heartbeating agents show as managed even with no approved inventory row.
- Confirm unmanaged/unauthorized state appears for the narrow detection rule only for agentless or untrusted discovery observations.
API Validation
curl -H "Authorization: Bearer <tenant-token>" \
http://127.0.0.1:8000/api/v1/assets
Smoke Validation
make test-asset-storage
make test-asset-ingestion
make smoke-asset-ingestion
Known Limitations
- No real collector runtime.
- No osquery/Fleet/OTel integration.
- No network scanning, SNMP, WMI, DHCP, ARP, or NetFlow ingestion.
- No topology mapping.
- No command execution.
Follow-Up Work
- Freeze agent enrollment and mTLS contracts.
- Implement active/passive discovery runtime from ADR-0012 after safety-limit, redaction, and source-confidence tests exist.
- Add approved collector runtimes after contracts are accepted.
Acceptance Criteria Mapping
| Acceptance criterion | Evidence |
|---|---|
| Heartbeat creates/updates assets | make test-asset-ingestion |
last_seen updates safely | Ingestion tests |
| Strong identifiers are preserved | Merge/confidence tests |
| Assets are visible through API/UI | Assets APIs and /console/assets |
| Enrolled agents are managed inventory | API returns management_status=managed and agent_managed=true after heartbeat |