Graph Asset Registry
Owner: Graph Indexer Guild Contract: Asset Registry v1 Schema version: asset-registry.v1 Primary code: src/Graph/StellaOps.Graph.Indexer/Assets/
Purpose
Graph owns the canonical asset registry used by the shared EU compliance substrate. The same asset node schema is consumed by the NIS2 N4 inventory plan, the DORA D2 Register of Information plan, and later TLPT scoping work under the one-registry rule in the EU sprint execution directions.
Asset Registry v1 adds deterministic asset graph nodes, ASSET_VERSION lineage edges, and append-only asset lifecycle events. It does not replace the SBOM dependency graph: software components remain component nodes and may be linked to image or service assets.
Architecture Overview
The implemented Graph-owned slice has four layers:
- Taxonomy and schema guardrails in
AssetRegistryV1,AssetOwnershipRefs, andAssetGraphDocumentFactory. - Source normalization in
AssetSourceAdapterfor SBOM scans, integration profiles, runtime service observations, and plugin catalog observations. - Deterministic merge and lineage in
AssetIndexTransformer, which emits asset nodes,ASSET_VERSIONedges, and asset registry events ordered byoccurred_at ASC, event_hash ASC. - Production source subscription through
AssetSourceSubscriptionProcessorandFileSystemAssetSourceFeed, which polls normalized*.asset-source-batch.jsonfiles from the configured spool and processes them throughAssetIndexProcessor. - Production wiring through
AddGraphIndexerPersistence(...), which registers PostgreSQL asset persistence plus the source-subscription bridge withGraph:AssetSourceSubscriptions:*options.
The current Graph-owned adapters materialize image, container, integration, service, and plugin assets from their approved observation shapes. The v1 taxonomy also admits host, but the current adapter slice only carries host references on container/service attributes; a production host-asset source feed is not implemented in the committed Graph adapter code.
Schema Reference
Authoritative schema and contract files:
- Contract: Asset Registry v1
- JSON schema: asset-registry-v1.schema.json
- Graph schema index: Graph Index Canonical Schema
- Architecture context: Graph architecture
V1 asset types are closed to:
hostcontainerimageserviceintegrationplugin
The enum is intentionally closed for v1. Credentials, secrets, people, teams, vulnerabilities, policy controls, findings, and software components are excluded asset types. The registry may store opaque references such as credential_ref, credentialRefs, ownerActorRef, and ownerTeamRef; it must not store secret values, names, email addresses, or dereferenced human PII.
Graph node mapping:
kind = assetid = assetId- Canonical key:
tenant,type,source,external_id attributes.schema_version = asset-registry.v1attributes.asset_attributes: source-specific string attributes after sensitive-field guardrailsattributes.ownership: opaque owner and credential references onlyvalid_to = lastSeenonly whentombstone = true
Asset lifecycle events:
- Event types:
asset.created,asset.updated,asset.removed - Edge kind:
ASSET_VERSION - Persistence table:
graph.asset_registry_events - Migration:
src/Graph/__Libraries/StellaOps.Graph.Indexer.Persistence/Migrations/003_asset_registry_events.sql - Startup migration registration:
src/Graph/__Libraries/StellaOps.Graph.Indexer.Persistence/Extensions/GraphIndexerPersistenceExtensions.cs
Events carry payload_hash, previous_event_hash, merkle_leaf_hash, and event_hash. Removed assets are tombstones, not physical deletes. Findings Ledger mirrors those source rows read-only into findings.asset_registry_events and preserves the Graph payload and hash fields unchanged.
Source Indexing Pipeline
The Graph Indexer owns normalization from approved observations into asset registry documents:
- SBOM scan observations create
imageassets and optionalcontainerassets. Container assets depend on the deterministic image asset ID. - Integration profile observations create
integrationassets. Credential material remains an opaquecredential_refandownership.credentialRefs; raw secret values are not accepted. - Runtime service observations create
serviceassets and may depend on deterministic image asset IDs or caller-supplied asset IDs. - Plugin catalog observations create
pluginassets for runtime-loaded or built-in plugin capabilities.
Within one indexing batch, duplicate observations for the same deterministic asset ID reduce to the latest observation by observedAt, eventOffset, and tombstone state. Removed observations produce tombstone nodes, asset.removed events, and ASSET_VERSION edges with valid_to set to the removal time.
Production subscriptions use the Graph-owned file spool bridge:
- Source services publish normalized
AssetSourceBatchJSON files throughIAssetSourceBatchPublisheror by writing the same canonical contract to the configuredGraph:AssetSourceSubscriptions:SpoolDirectory. - Graph Indexer persistence registration binds
Graph:AssetSourceSubscriptions:*, polls the spool onGraph:AssetSourceSubscriptions:PollInterval, defaults to and caps polling at 30 minutes, and runs each batch through the sameAssetIndexProcessorused by direct callers. - Successfully processed files move to
processed/; failed files move tofailed/after the error is logged. - Source services remain responsible for translating their local facts into the approved observation shapes without dereferencing secrets or human PII.
The production bridge is intentionally file-based rather than a cross-module table write: Scanner, Integrations, Signals, and plugin hosts can publish the normalized contract without taking a dependency on Graph persistence or writing the graph PostgreSQL schema directly.
Sync Envelope
The formal offline/federated delta sync contract is Asset Inventory Sync v1. It wraps the Graph asset event stream without redefining asset payloads:
- Export existing
asset-registry.v1events. - Order entries by
occurred_at ASC, event_hash ASC. - Canonicalize the bundle with Graph Indexer canonical JSON.
- Compute
bundle_hashover the envelope excludingbundle_idandbundle_hash. - On import, reconstruct every
AssetRegistryEventfrom the included payload and require existing payload/event/Merkle hashes to match. - Apply only when the local per-asset chain contains the predecessor named by
previous_event_hash, or skip the event when the same event hash already exists locally.
The local implementation is in src/Graph/StellaOps.Graph.Indexer/Assets/AssetInventorySyncEnvelope.cs. Focused tests in src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/AssetInventorySyncEnvelopeTests.cs cover canonical serialization, idempotent apply, tamper detection, and missing-predecessor rejection.
Downstream Compliance Use
NIS2 inventory:
- The registry is the source intended for NIS2 area 12 asset inventory and control evidence links.
- KPI references for inventory freshness, owner coverage, and unknown critical assets are documented in the NIS2 KPI catalog.
- The
/inventoryConsole page remains TODO in sprint 20260430_072.
DORA RoI:
- DORA RoI projections are planned to consume
integrationandserviceassets plus operator-entered contract metadata. - Implemented integration metadata lives in the Integrations architecture dossier and sprint 20260430_120.
- RoI template encoders, signed bundle export, and the operator runbook remain TODO in sprints 20260430_121, 20260430_122, 20260430_123, and 20260430_124.
TLPT:
- TLPT scoping is planned to query the same registry by tenant, criticality, dependencies, and tombstone state.
- The scoper and TLPT evidence pack are not part of the committed asset registry slice.
Implementation Status
Implemented:
- Asset Registry v1 contract and JSON schema.
- Closed v1 taxonomy and excluded-type handling.
- Deterministic asset node and
ASSET_VERSIONedge creation. - Append-only Graph-side asset event persistence with startup migrations.
- Graph-owned source adapter, transformer, processor, in-memory test store, and PostgreSQL event store.
- Graph-owned source subscription bridge with file-spool feed, canonical batch publisher, production persistence registration, 30-minute default poll interval, processed/failed file handling, and PostgreSQL integration tests proving registration plus first-scan fresh-DB population.
- Local
asset-inventory-sync.v1delta bundle codec and applier for deterministic offline/federated asset event replay. - Offline CLI inventory commands over a deterministic
asset-registry.v1input file in sprint 20260430_073. - Findings Ledger visibility projection for Graph asset created/updated/removed events in sprint 20260430_070.
Blocked or not yet implemented:
- Scanner, Integrations, Signals, and plugin-host services still need module-local emitters when they choose not to write the normalized spool contract directly.
- Live
/graph/assetslist/show/export runtime API and CLI backend-client methods are not implemented; sprint 073 therefore fails closed without--input. /inventoryConsole page is TODO.- Host asset materialization is schema-supported but not produced by the current Graph-owned source adapters.
Offline Expectations
- No external fetches are required for schema validation, replay, or tests.
- Schema changes are pinned by
asset-registry.v1; breaking changes require a new schema version and migration. - Determinism tests are in:
src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/AssetRegistrySchemaTests.cssrc/Graph/__Tests/StellaOps.Graph.Indexer.Tests/AssetIndexerPipelineTests.cssrc/Graph/__Tests/StellaOps.Graph.Indexer.Persistence.Tests/AssetRegistryEventStoreTests.cs
Sprint Evidence
- Sprint 20260430_070 - Graph Asset Registry v1 Schema
- Sprint 20260430_071 - Graph Asset indexer pipeline
- Sprint 20260430_072 - Web inventory page
- Sprint 20260430_073 - CLI inventory export
