RichGraph v1 — Reachability Graph Schema

Contract ID: CONTRACT-RICHGRAPH-V1-015 Status: Published  ·  Version: 1.0.0  ·  Published: 2025-12-05 Owners: Scanner Guild, Signals Guild, BE-Base Platform Guild

Overview

richgraph-v1 is the canonical schema Stella Ops uses for function-level reachability analysis: the call graph that lets the scanner prove whether a vulnerable symbol is actually reachable from an application’s entry points. This contract defines the data model, hash algorithms, determinism rules, content- addressable storage (CAS) layout, and DSSE attestation that together make a reachability claim reproducible and verifiable.

Audience: engineers building or consuming reachability output — the Scanner reachability analyzer, the Signals runtime, symbol servers, and any service that stores, attests, or replays a reachability graph.


Schema Definition

richgraph-v1 Document Structure

{
  "schema": "richgraph-v1",
  "analyzer": {
    "name": "scanner.reachability",
    "version": "0.1.0",
    "toolchain_digest": "sha256:..."
  },
  "nodes": [
    {
      "id": "sym:java:base64url...",
      "symbol_id": "sym:java:base64url...",
      "lang": "java",
      "kind": "method",
      "display": "com.example.Foo.bar(String)",
      "code_id": "code:java:base64url...",
      "code_block_hash": "sha256:deadbeef...",
      "symbol": { "mangled": "_Z15ssl3_read_bytes", "demangled": "ssl3_read_bytes", "source": "DWARF", "confidence": 0.98 },
      "purl": "pkg:maven/com.example/foo@1.0.0",
      "build_id": "gnu-build-id:...",
      "symbol_digest": "sha256:...",
      "evidence": ["import", "disasm"],
      "attributes": {"key": "value"}
    }
  ],
  "edges": [
    {
      "from": "sym:java:...",
      "to": "sym:java:...",
      "kind": "call",
      "purl": "pkg:maven/com.example/bar@2.0.0",
      "symbol_digest": "sha256:...",
      "confidence": 0.9,
      "evidence": ["reloc", "runtime"],
      "candidates": []
    }
  ],
  "roots": [
    {
      "id": "sym:java:...",
      "phase": "runtime",
      "source": "main"
    }
  ]
}

Node Schema

FieldTypeRequiredDescription
idstringYesUnique node identifier (typically same as symbol_id)
symbol_idstringYesCanonical SymbolID (format: sym:{lang}:{base64url-sha256})
langstringYesLanguage: java, dotnet, go, node, rust, python, ruby, php, binary, shell
kindstringYesSymbol kind: method, function, class, module, trait, struct
displaystringNoHuman-readable demangled name
code_idstringNoCodeID for name-less symbols (format: code:{lang}:{base64url-sha256})
code_block_hashstringNoHash of the code block for stripped/heuristic nodes (algorithm-prefixed hex)
purlstringNoPackage URL of containing package
build_idstringNoGNU build-id, PE GUID, or Mach-O UUID
symbol_digeststringNoSHA-256 of the symbol_id (format: sha256:{hex})
symbolobjectNoSymbol metadata {mangled?, demangled?, source?, confidence?} with source ∈ {DWARF,PDB,SYM,NONE} and confidence in [0,1]
evidencestring[]NoEvidence sources (sorted): import, reloc, disasm, runtime
attributesobjectNoAdditional key-value metadata (sorted by key)

Edge Schema

FieldTypeRequiredDescription
fromstringYesSource node ID
tostringYesTarget node ID
kindstringYesEdge type: call, virtual, indirect, data, init
purlstringNoPackage URL of callee
symbol_digeststringNoSHA-256 of callee symbol_id
confidencenumberYesConfidence [0.0-1.0]: certain=1.0, high=0.9, medium=0.6, low=0.3
evidencestring[]NoEvidence sources (sorted)
candidatesstring[]NoAlternative resolution candidates (sorted)
gate_multiplier_bpsnumberNoCombined gate multiplier for this edge in basis points (10000 = 100%)
gatesobject[]NoGate annotations (sorted)

Gate Schema (optional)

When gates is present, each element follows:

FieldTypeRequiredDescription
typestringYesGate type: authRequired, featureFlag, adminOnly, nonDefaultConfig
guard_symbolstringYesSymbol where gate was detected
source_filestringNoSource file location (if available)
line_numbernumberNoLine number (if available)
detection_methodstringYesDetector/method identifier
confidencenumberYesConfidence [0.0-1.0]
detailstringYesHuman-readable description

Root Schema

FieldTypeRequiredDescription
idstringYesNode ID designated as entry point
phasestringYesExecution phase: runtime, load, init, test
sourcestringNoEntry point source (e.g., main, DT_INIT, .ctors)

Hash Algorithms

Summary

ComponentAlgorithmFormatExample
graph_hashBLAKE3-256blake3:{hex}blake3:a1b2c3d4...
symbol_digestSHA-256sha256:{hex}sha256:e5f6a7b8...
symbol_id fragmentSHA-256base64url-no-padsym:java:abc123...
code_id fragmentSHA-256base64url-no-padcode:java:xyz789...

Graph Hash (BLAKE3-256)

The graph hash provides content-addressable identification:

graph_hash = "blake3:" + hex(BLAKE3-256(canonical_json_bytes))

Rationale: BLAKE3 chosen for:

Symbol Digest (SHA-256)

Symbol digests use SHA-256 for interoperability:

symbol_digest = "sha256:" + hex(SHA-256(utf8(symbol_id)))

SymbolID and CodeID Fragments

Internal fragments use SHA-256 with base64url encoding:

fragment = base64url_no_pad(SHA-256(utf8(canonical_tuple)))
symbol_id = "sym:{lang}:{fragment}"
code_id = "code:{lang}:{fragment}"

Determinism Rules

All outputs must be reproducible. The Trimmed() operation enforces canonical ordering:

Ordering Rules

  1. Nodes: Sort by id (ordinal string comparison)
  2. Edges: Sort by (from, to, kind) in that order (ordinal)
  3. Roots: Sort by id (ordinal)
  4. Evidence arrays: Sort alphabetically (ordinal)
  5. Candidates arrays: Sort alphabetically (ordinal)
  6. Attributes objects: Sort keys alphabetically (ordinal)

Normalization Rules

  1. Trim whitespace: All string values trimmed
  2. Empty to null: Empty strings become null/omitted
  3. Confidence clamping: Values clamped to [0.0, 1.0]
  4. Default values:
    • kind defaults to "call" for edges
    • phase defaults to "runtime" for roots
    • analyzer.name defaults to "scanner.reachability"
    • analyzer.version defaults to "0.1.0"

JSON Serialization


CAS Layout

Graph Storage

cas://reachability/graphs/{blake3}          # Graph body (canonical JSON)
cas://reachability/graphs/{blake3}.dsse     # DSSE envelope

Edge Bundle Storage (Optional)

For runtime hits, init-array roots, and contested edges:

cas://reachability/edges/{graph_hash}/{bundle_id}       # Edge bundle body
cas://reachability/edges/{graph_hash}/{bundle_id}.dsse  # DSSE envelope

Metadata Storage

{output_root}/reachability_graphs/{analysis_id}/richgraph-v1.json  # Graph body
{output_root}/reachability_graphs/{analysis_id}/meta.json          # Metadata

meta.json structure:

{
  "schema": "richgraph-v1",
  "graph_hash": "blake3:...",
  "files": [
    {"path": "...", "hash": "blake3:..."}
  ]
}

DSSE Integration

Predicate Types

PredicatePurpose
stella.ops/graph@v1Graph-level attestation
stella.ops/edgeBundle@v1Edge bundle attestation

Graph DSSE (Mandatory)

Every richgraph-v1 document requires a DSSE envelope:

{
  "payloadType": "application/vnd.stellaops.graph+json",
  "payload": "<base64(canonical_graph_json)>",
  "signatures": [...]
}

Subject: cas://reachability/graphs/{blake3}

Rekor Integration


SymbolID Construction

Format

sym:{lang}:{base64url_sha256_no_pad}

Per-Language Canonical Tuples

LanguageTuple Components (NUL-separated)
Java{package}\0{class}\0{method}\0{descriptor} (lowercased)
.NET{assembly}\0{namespace}\0{type}\0{member_signature}
Go{module}\0{package}\0{receiver}\0{func}
Node/Deno{pkg_or_path}\0{export_path}\0{kind}
Rust{crate}\0{module}\0{item}\0{mangled?}
Python{pkg_or_path}\0{module}\0{qualified_name}
Ruby{gem_or_path}\0{module}\0{method}
PHP{composer_pkg}\0{namespace}\0{qualified_name}
Binary{file_hash}\0{section}\0{addr}\0{name}\0{linkage}\0{code_block_hash?}
Shell{script_rel_path}\0{function_or_cmd}
Swift{module}\0{type}\0{member}\0{mangled?}

CodeID Construction

Format

code:{lang}:{base64url_sha256_no_pad}

Use Cases

CodeIDs provide stable identifiers when symbol names are unavailable:


Implementation Status

Existing Implementation

ComponentLocationStatus
RichGraph modelsrc/Scanner/__Libraries/StellaOps.Scanner.Reachability/RichGraph.csImplemented
SymbolId buildersrc/Scanner/__Libraries/StellaOps.Scanner.Reachability/SymbolId.csImplemented
CodeId buildersrc/Scanner/__Libraries/StellaOps.Scanner.Reachability/CodeId.csImplemented
RichGraphWritersrc/Scanner/__Libraries/StellaOps.Scanner.Reachability/RichGraphWriter.csNeeds BLAKE3
DSSE predicatessrc/Attestor/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.csImplemented

Required Changes

ChangePriorityNotes
Update RichGraphWriter to use BLAKE3P0Currently uses SHA256 for graph_hash
Add meta.json hash prefixP1Use blake3: prefix
CAS adapter for graph storageP1Implement cas://reachability/graphs/{blake3} paths

Decision Checklist

This contract resolves the following decisions from the 2025-12-02 alignment meeting:

DecisionChoiceRationale
Graph hash algorithmBLAKE3-256Speed + security
Symbol digest algorithmSHA-256Interoperability
CAS path schemecas://reachability/graphs/{blake3}Content-addressable
DSSE required for graphsYes (mandatory)Provenance chain
DSSE for edge bundlesOptional (capped)Rekor volume control
JSON canonicalizationSorted keys, compactDeterminism
Hash prefix format{alg}:{hex}Explicit algorithm ID

Validation Rules

Schema Validation

  1. schema must equal "richgraph-v1"
  2. nodes array must not be empty
  3. All node id values must be unique
  4. All edge from/to must reference existing nodes
  5. All root id values must reference existing nodes
  6. confidence must be in range [0.0, 1.0]

Hash Validation

  1. graph_hash must match BLAKE3-256 of canonical JSON
  2. symbol_digest must match SHA-256 of symbol_id
  3. SymbolID fragments must match SHA-256 of canonical tuple

Migration Path

From Current Implementation

  1. RichGraphWriter: Replace ComputeSha256 with ComputeBlake3 for graph hash
  2. meta.json: Update hash format from sha256: to blake3:
  3. Existing graphs: Recompute hashes on next scan (no migration needed)

Compatibility


Reference Implementation

Canonical JSON Writer

// From RichGraph.cs - Trimmed() enforces canonical ordering
public RichGraph Trimmed()
{
    var nodes = Nodes.OrderBy(n => n.Id, StringComparer.Ordinal).ToList();
    var edges = Edges
        .OrderBy(e => e.From, StringComparer.Ordinal)
        .ThenBy(e => e.To, StringComparer.Ordinal)
        .ThenBy(e => e.Kind, StringComparer.Ordinal)
        .ToList();
    var roots = Roots.OrderBy(r => r.Id, StringComparer.Ordinal).ToList();
    return this with { Nodes = nodes, Edges = edges, Roots = roots };
}

BLAKE3 Graph Hash (Required Update)

// Replace in RichGraphWriter.cs
private static string ComputeBlake3(byte[] bytes)
{
    using var blake3 = Blake3.Hasher.New();
    blake3.Update(bytes);
    var hash = blake3.Finalize();
    return "blake3:" + Convert.ToHexString(hash.AsSpan()).ToLowerInvariant();
}


Changelog

VersionDateAuthorChanges
1.0.02025-12-05Scanner GuildInitial contract from alignment meeting