GraphIR is the stable machine-readable architecture graph emitted by:
rv graph <path> --format json
rv graph <path> --format json --out graph.json
rv graph <path> --contracts .architon/contracts.yaml --format jsonrv graph uses the same project detection, import, metadata, contract loading,
voltage propagation, and deterministic rule evaluation pipeline as rv scan.
It does not require Studio and does not call AI.
GraphIR is for Studio and other graph renderers. It is not a replacement for the scan report: the scan report remains the primary CI/reporting artifact, while GraphIR embeds the same findings so renderers can highlight affected graph elements directly.
Top-level object:
{
"graph_version": "1",
"rv_version": "v0.6.0-dev",
"input_path": "project-or-file-path",
"summary": {
"violations": 0,
"warnings": 0,
"infos": 0,
"findings": 0,
"has_failures": false,
"user_contracts_loaded": 0,
"built_in_contracts_loaded": 0,
"active_user_requirements": 0
},
"nodes": [],
"edges": [],
"rails": [],
"interfaces": [],
"findings": [],
"findings_index": {}
}summary counts embedded scan findings by severity. ERROR increments
violations, WARN increments warnings, and INFO increments infos.
The contract counts and has_failures are copied from the same canonical scan
result used to build the graph.
Nodes are physical components from the imported design.
{
"id": "U1",
"ref": "U1",
"label": "U1 ESP32-WROOM-32",
"type": "mcu",
"contract_coverage": "full",
"violations": 0,
"warnings": 0,
"nets": ["/I2C_SDA", "+3V3"],
"metadata": {
"value": "ESP32-WROOM-32",
"footprint": "RF_Module:ESP32-WROOM-32",
"mpn": "ESP32-WROOM-32",
"manufacturer": "Espressif"
}
}Node type is one of:
mcu, sensor, motor_driver, regulator, connector, passive, power_source, unknown
contract_coverage is one of:
full, partial, missing, unknown
Edges are electrical relationships between components on shared nets. In
GraphIR v1 these are pairwise component-to-component edges, sorted by stable
edge ID. Renderers may group them by interface, type, domain, or net to
avoid visual clutter.
{
"id": "edge_U1_U2_I2C_SDA",
"source": "U1",
"target": "U2",
"net": "/I2C_SDA",
"type": "i2c",
"domain": "data",
"interface": "I2C_1",
"voltage_v": 3.3,
"violations": 0,
"warnings": 0,
"finding_ids": []
}Edge type is one of:
power, i2c, spi, uart, gpio, analog, motor, unknown
Edge domain is one of:
power, data, control, unknown
voltage_v is omitted when no deterministic voltage is known for the net.
interface is always present. For power edges it is the rail/net name; for
unknown edges it is "unknown".
Rails are power domains inferred from net names, pin roles, metadata, and voltage propagation.
{
"name": "/+3V3",
"voltage_v": 3.3,
"source_ref": "U2",
"consumers": ["U1", "U3"],
"violations": 0,
"warnings": 0,
"finding_ids": []
}voltage_v is emitted for explicit, propagated, or inferred rails. source_ref
is set only when a plausible power-producing source is known, such as a
regulator, power source, or contract-backed power output. Passive pull-ups,
loads, and arbitrary first components are not used as rail sources; unknown
sources are emitted as an empty string.
Interfaces group bus-like electrical relationships such as I2C, SPI, and UART. They are intended for Studio-level rendering when pairwise edges would be too dense.
{
"id": "I2C_1",
"type": "i2c",
"nets": ["/I2C_SCL", "/I2C_SDA"],
"participants": ["U1", "U2"],
"pullups": [
{
"ref": "R1",
"net": "/I2C_SDA",
"resistance_ohms": 4700,
"to_net": "/+3V3"
}
],
"violations": 0,
"warnings": 0,
"finding_ids": []
}pullups is deterministic topology metadata. GraphIR does not invent pull-up
findings; it only embeds and links findings produced by the scan pipeline.
Findings are the same deterministic scan findings produced by rv scan, with
stable IDs assigned for graph linking.
{
"id": "pullup_ohms_01",
"rule_id": "pullup_ohms",
"contract_id": "i2c_pullups",
"contract_source": "user_yaml",
"severity": "ERROR",
"message": "Observed: no pull-up resistor found on net /I2C_SDA. Expected: pull-up resistor between 2.2k and 10k to a compatible positive rail.",
"component_ref": "",
"net": "/I2C_SDA",
"pin": "",
"requirement": "pullup_ohms",
"fix": "Use pull-ups between 2.2k and 10k to the bus voltage rail, commonly 4.7k for a 3.3V I2C bus.",
"why_this_matters": "I2C lines are open-drain and must idle high. Without pull-ups, SDA/SCL may never reach a valid HIGH level, so devices may not communicate.",
"provenance": "source=user_yaml; source_id=i2c_pullups"
}Severity is one of ERROR, WARN, or INFO.
findings_index maps each finding ID to the graph elements affected by that
finding.
{
"pullup_ohms_01": {
"node_ids": ["R1"],
"edge_ids": ["edge_R1_U1_I2C_SDA", "edge_R1_U2_I2C_SDA"],
"rail_names": [],
"interface_ids": ["I2C_1"]
}
}Rollup rules:
- If a finding has
component_ref, it attaches to the matching node. - If a finding has
net, it attaches to all graph edges for that net. - If a finding net is a rail, it attaches to the matching rail.
- If a finding mentions multiple nets, each matching edge or rail can be linked.
- Bus-level findings attach to matching interface objects and relevant bus edges.
ERRORincrementsviolations;WARNincrementswarnings;INFOonly adds finding links.
Finding IDs are stable within one graph. When multiple report findings share the
same rule ID, GraphIR appends deterministic numeric suffixes such as
pullup_ohms_01.
{
"graph_version": "1",
"rv_version": "v0.6.0-dev",
"input_path": "examples/custom-contracts/exports/custom_contracts.net",
"summary": {
"violations": 1,
"warnings": 0,
"infos": 0,
"findings": 1
},
"nodes": [
{
"id": "U1",
"ref": "U1",
"label": "U1 MCU_A",
"type": "mcu",
"contract_coverage": "partial",
"violations": 0,
"warnings": 0,
"nets": ["/+3V3", "/I2C_SDA", "GND"],
"metadata": {
"value": "MCU_A",
"footprint": "",
"mpn": "MCU_A",
"manufacturer": ""
}
}
],
"edges": [
{
"id": "edge_U1_U2_I2C_SDA",
"source": "U1",
"target": "U2",
"net": "/I2C_SDA",
"type": "i2c",
"domain": "data",
"interface": "I2C_MAIN",
"voltage_v": 3.3,
"violations": 1,
"warnings": 0,
"finding_ids": ["pullup_ohms_01"]
}
],
"rails": [
{
"name": "/+3V3",
"voltage_v": 3.3,
"source_ref": "",
"consumers": ["U1", "U2"],
"violations": 0,
"warnings": 0,
"finding_ids": []
}
],
"interfaces": [
{
"id": "I2C_MAIN",
"type": "i2c",
"nets": ["/I2C_SCL", "/I2C_SDA"],
"participants": ["U1", "U2"],
"pullups": [],
"violations": 1,
"warnings": 0,
"finding_ids": ["pullup_ohms_01"]
}
],
"findings": [
{
"id": "pullup_ohms_01",
"rule_id": "pullup_ohms",
"contract_id": "i2c_pullups",
"contract_source": "user_yaml",
"severity": "ERROR",
"message": "Net /I2C_SDA has no pull-up resistor in scope",
"component_ref": "",
"net": "/I2C_SDA",
"pin": "",
"requirement": "pullup_ohms",
"fix": "Add a pull-up resistor from /I2C_SDA to the I2C rail between 2200 and 10000 ohms.",
"provenance": "source=user_yaml; source_id=i2c_pullups"
}
],
"findings_index": {
"pullup_ohms_01": {
"node_ids": [],
"edge_ids": ["edge_U1_U2_I2C_SDA"],
"rail_names": [],
"interface_ids": ["I2C_MAIN"]
}
}
}Studio should treat nodes, edges, rails, and interfaces as renderable
graph data:
- Use
node.idas the graph node key andnode.refas the schematic reference. - Use
edge.sourceandedge.targetto connect physical components. - Use
edge.netas the electrical net represented by an edge. - Group or style edges by
edge.interface,edge.type, andedge.domain. - Prefer
interfacesfor bus rendering when pairwise edges would be visually dense. - Render rails from
rails; rails may also appear as power edges when connected components share a rail net. - Use
violationsandwarningson nodes, edges, rails, and interfaces for badges. - Use
findingsfor finding detail panels. - Use
finding_idsandfindings_indexto highlight all graph elements affected by a selected finding.
Studio should not parse human scan output and should not infer additional electrical meaning from labels. The stable fields are the typed fields above.
GraphIR graph_version is currently "1". For a given CLI build, input files,
metadata, contracts, and command flags, output is deterministic across repeated
runs.
GraphIR v1 preserves stable ordering:
- nodes sort by component reference
- edges sort by edge ID
- rails sort by rail name
- interfaces sort by interface ID
- findings preserve canonical scan finding order
Additive fields may appear in future graph_version "1" outputs. Existing
fields, enum values, and link semantics will not be removed or redefined without
incrementing graph_version.