Graph overlay and tile cache schema (draft)
Status: DRAFT / NOT IMPLEMENTED (forward design). As of this revision no code in
src/Graph(or anywhere insrc/) produces, persists, or consumes a cached tile document of the shape described below. A repo-wide search for the distinguishing markers of this schema (graph-tile::,bbox,minX,verdictAt,statementId) returns no hits insrc/. The only overlay-export artifact that exists today isGraphOverlayExporter(src/Graph/StellaOps.Graph.Indexer/Analytics/GraphOverlayExporter.cs), which writes unrelatedoverlays/clusters.ndjson,overlays/centrality.ndjson, and anoverlays/manifest.json— not this tile cache. Treat this file as a target design, not a description of current behavior.
Overview
This document describes a proposed cached/materialized tile representation for gateway/UI components to store graph tiles alongside overlay data. It is a forward design and is not yet wired into any service.
This cache schema is distinct from the streaming NDJSON tile protocol that the Graph API actually implements today:
- Streaming API contract (draft OpenAPI):
graph-gateway-spec-draft.yaml - Streaming API reference (reconciled): Graph Gateway API
- Sample cached tile (illustrative, matches this draft):
overlay-sample.json
Relationship to the implemented streaming contract
The live Graph API (src/Graph/StellaOps.Graph.Api) does not emit the overlay arrays shown here. Instead, POST /graph/query and POST /graph/paths stream NDJSON tile envelopes, and overlays — when includeOverlays is requested — are attached per node, keyed by overlay kind, inside the node tile rather than collected into top-level arrays. The implemented contracts are:
NodeTile.Overlaysis aDictionary<string, OverlayPayload>(src/Graph/StellaOps.Graph.Api/Contracts/SearchContracts.cs,NodeTile/OverlayPayload).OverlayPayloadisrecord OverlayPayload(string Kind, string Version, object Data)— i.e. each overlay carries a free-formdataobject (containing fields such asoverlayId,subject,decision/status,explainTrace), produced byInMemoryOverlayServicefor kindspolicy,vex, andaoc.
So the per-node streaming overlay shape ({ kind, version, data: { … } }) is different from the cache overlay-record shape ({ nodeId, badge, policyId, verdictAt }) defined in the Schema section below. If this cache is ever implemented, that mapping must be reconciled.
Cached tile shape
A cached tile document is a single JSON object with:
version: cache schema version (string; bump only for breaking changes)tenantId: tenant partition for the cache entrytile: tile identity + spatial key (id,bbox,zoom) and cache validator (etag)nodes: node recordsedges: edge recordsoverlays: overlay arrays keyed by overlay kind (policy,vex,aoc)telemetry: generation/caching metadata
Note on
bbox. Graph tiles are not geospatial; the geographic-looking coordinate names (minX/minY/maxX/maxYwith longitude/latitude-shaped sample values) are illustrative of an abstract 2-D layout extent, not real map coordinates. No layout/extent producer for this field exists in code yet.
Schema (draft)
{
"version": "0.2",
"tenantId": "tenant-default",
"tile": {
"id": "graph-tile::<scope>::<hash>::z8/x12/y5",
"bbox": { "minX": -122.41, "minY": 37.77, "maxX": -122.38, "maxY": 37.79 },
"zoom": 8,
"etag": "c0ffee-etag"
},
"nodes": [
{
"id": "asset:...",
"kind": "asset|component|vuln",
"label": "optional display label",
"severity": "critical|high|medium|low|info",
"reachability": "reachable|unreachable|unknown",
"attributes": {}
}
],
"edges": [
{
// NOTE: lower-case `type` values below are this draft's convention. The
// implemented graph stores edge kinds upper-case (CONTAINS, DEPENDS_ON —
// see GraphInspectorTransformer / SbomIngestTransformer), and the
// streaming EdgeTile.Kind defaults to "depends_on". "evidence" is not a
// recognized edge kind in current code.
"id": "edge-1",
"source": "nodeId",
"target": "nodeId",
"type": "depends_on|contains|evidence",
"weight": 0.0,
"attributes": {}
}
],
"overlays": {
"policy": [
{ "nodeId": "nodeId", "badge": "pass|warn|fail|waived", "policyId": "policy://...", "verdictAt": "2025-01-02T03:04:05Z" }
],
"vex": [
{ "nodeId": "nodeId", "state": "not_affected|fixed|under_investigation|affected", "statementId": "vex:...", "lastUpdated": "2025-01-02T03:04:05Z" }
],
"aoc": [
{ "nodeId": "nodeId", "status": "pass|fail|warn", "lastVerified": "2025-01-02T03:04:05Z" }
]
},
"telemetry": { "generationMs": 0, "cache": "hit|miss", "samples": 0 }
}
Overlay-kind / field reconciliation (draft vs. implemented). The overlay kinds in this cache (
policy,vex,aoc) match the kinds emitted today byInMemoryOverlayService. Note the following divergences against current code, to be resolved if this cache is implemented:
- The implemented VEX overlay uses a field named
status(valuesaffected,under_investigation,not_affected) — this draft renames it tostateand adds afixedvalue that the service does not currently emit.- The implemented policy overlay carries a
decisionfield (pass/warn/fail) — this draft renames it tobadgeand adds awaivedvalue.- The streaming gateway draft (
graph-gateway-spec-draft.yaml) enumerates overlay kinds aspolicy,vex,advisory(notaoc); the live overlay service emitsaocrather thanadvisory. The two enumerations are not yet consistent.
Determinism rules
- Arrays are pre-sorted:
nodesbyidedgesbyid- overlay arrays by
nodeIdthen secondary key (policyId,statementId, etc.)
- Timestamps are ISO-8601 UTC.
- Hashes are lower-case hex.
Constraints (draft)
- Max nodes per tile: 2,000
- Max edges per tile: 4,000
- Zoom range: 0-12
