Graph Architecture

Derived from Epic 5 – SBOM Graph Explorer.

The Graph module is the generic SBOM dependency/service graph store for the Stella Ops release control plane. It ingests SBOMs, advisories, VEX statements, and asset observations into a content-addressed node/edge model, then serves query, path, diff, lineage, overlay, and export surfaces to the Console, CLI, and SDK. This document captures the core model, ingestion pipelines, API surface, storage, and observability as implemented.

Scope note. This module (src/Graph/) owns the generic dependency/service graph store — nodes, edges, overlays, lineage, and the asset registry. It is distinct from src/ReachGraph/ (the reachability subgraph), which consumes Graph data rather than owning it. The graph:simulate scope and AI-assisted recommendations referenced below are roadmap items not yet enforced by this service.

0) Components

Shipped as a single container (stellaops-graph-api, slot 20). Cartographer (formerly slot 21) has been merged in; cartographer.stella-ops.local is now a network alias on this container. The module is composed of:

1) Core model

2) Pipelines

  1. Ingestion: SBOM Service emits SBOM snapshots (sbom_snapshot events) captured by the Graph Indexer (now hosted inside graph-api; Cartographer merged). Ledger lineage references become SBOM_VERSION_OF + SBOM_LINEAGE_* edges. Advisories/VEX from Concelier/Excititor generate edge updates, policy runs attach overlay metadata. Asset source observations from SBOM scans, integration profiles, runtime services, and plugin catalogs normalize through the Graph-owned asset indexer pipeline into asset nodes, append-only asset events, and ASSET_VERSION lineage edges. Production asset source subscriptions poll normalized *.asset-source-batch.json files from Graph:AssetSourceSubscriptions:SpoolDirectory on a default and capped 30-minute interval, then process them through AssetIndexProcessor so source services do not write Graph tables directly.
  2. ETL: Normalises nodes/edges into canonical IDs (GraphIdentity.ComputeNodeId/ComputeEdgeId — tenant-scoped, kind-prefixed, Base32-Crockford SHA-256 of the sorted identity tuple; gn:/ge: prefixes), deduplicates, enforces tenant partitions, and writes document-JSON adjacency rows to PostgreSQL (graph.graph_nodes/graph.graph_edges). A graph-DB backend is a roadmap option, not shipped (see §4).
  3. Overlay computation: Roadmap. The intended design has batch workers build materialised views for frequently used queries (impact lists, saved queries, policy overlays) and store them as immutable blobs for Offline Kit exports. Today only inline, per-request overlays exist (Testing only — see §1 and §3.1); there is no durable overlay materialisation worker yet.
  4. Diffing: graph_diff jobs compare two snapshots (e.g., pre/post deploy) and generate signed diff manifests for UI/CLI consumption.
  5. Analytics (Runtime & Signals 140.A): background workers run Louvain-style clustering + degree/betweenness approximations on ingested graphs, emitting overlays per tenant/snapshot and writing cluster ids back to nodes when enabled.

3) APIs

3.1) Current runtime posture (Sprint 20260416_003)

3.2) Tenant and auth resolution contract (Sprint 20260222.058)

3.3) Edge Metadata Contracts

The edge metadata system provides explainability for graph relationships:

3.4) Localization runtime contract (Sprint 20260224_002)

3.5) Authorization scopes

The three authorization policies (Graph.ReadOrQuery, Graph.Query, Graph.Export) map to OAuth scopes via Security/GraphPolicies.cs; a request is admitted if it carries any scope in the set:

PolicyAccepted scopesUsed by
Graph.ReadOrQuerygraph:read or graph:querysearch, lineage, edge metadata (single/batch/by-reason/by-evidence), asset list/query/get, compatibility GETs
Graph.Querygraph:queryquery, paths, diff, edges-on-path, compatibility /paths, saved-view create/delete
Graph.Exportgraph:exportexport (start + download), asset export, compatibility /graphs/{id}/export

4) Storage considerations

5) Offline & export

6) Observability

7) Rollout notes

Local testing note

Set STELLAOPS_TEST_POSTGRES_CONNECTION to a reachable PostgreSQL instance before running tests/Graph/StellaOps.Graph.Indexer.Tests. The test harness falls back to Host=127.0.0.1;Port=5432;Database=stellaops_test, then Testcontainers for PostgreSQL, but the CI workflow requires the environment variable to be present to ensure upsert coverage runs against a managed database. Use STELLAOPS_GRAPH_SNAPSHOT_DIR (or the AddSbomIngestPipeline options callback) to control where graph snapshot artefacts land during local runs.

Refer to the module README and implementation plan for immediate context, and update this document once component boundaries and data flows are finalised.