src/plugins.d/FUNCTION_TOPOLOGY_DEVELOPER_GUIDE.md
This document defines the production topology payload contract for Netdata Functions. It is the source of truth for new topology producers and for the Cloud topology aggregator.
The JSON Schema is FUNCTION_TOPOLOGY_SCHEMA.json.
Topology payloads describe relationships between observed entities:
The payload is optimized for two consumers:
It is not optimized for reconstructing compatibility payloads. Parity with compatibility consumers is a test concern, implemented by test-side projection code.
Do not put these in production topology payloads:
Type-level labels, UI-owned visual tokens, safe label policies, and small column descriptions are acceptable when they help render generic topology UI. Define them once per type or column, not once per row.
The schema has six planes:
Graph links are intentionally smaller than evidence. A graph link may have one evidence row or tens of thousands of evidence rows.
Topology Functions return a normal Function envelope with type: "topology":
{
"v": 3,
"status": 200,
"type": "topology",
"has_history": false,
"update_every": 5,
"expires": 10,
"data": {
"schema_version": "netdata.topology.v1",
"producer": {
"source": "network-connections",
"instance": "local",
"node_id": "node-uuid",
"machine_guid": "machine-guid",
"plugin": "network-viewer.plugin"
},
"collected_at": "2026-05-09T10:00:00Z",
"dictionaries": {
"strings": ["process", "endpoint", "tcp", "inbound", "established"]
},
"types": {
"actor_types": {},
"link_types": {}
},
"presentation": {
"selection": {
"actor_click": {"mode": "highlight_connections"}
},
"legend": {
"actors": [],
"links": [],
"ports": []
}
},
"actors": {
"rows": 0,
"columns": [],
"values": []
},
"links": {
"rows": 0,
"columns": [],
"values": []
}
}
}
v is the optional Function transport protocol version. It belongs to the
response envelope, not to data, and is commonly 3 for Functions that accept
POST JSON parameters.
schema_version is the topology contract version. It is not the producer
version. Producers may expose their own version in producer.agent_version,
producer.plugin, or producer.capabilities.
Function info responses are response metadata, not topology payloads. They
may advertise accepted_params, required_params, and help text without a
data object. Validate only full topology responses against
FUNCTION_TOPOLOGY_SCHEMA.json.
Use the request parameter __topology_mode when a topology producer has a real
detailed vs aggregated output difference. Supported values are detailed and
aggregated.
Do not expose a detailed/aggregated selector for mode-invariant topologies. SNMP/L2 and streaming currently emit the same topology grain for both use cases and should not advertise a mode option until a real difference exists.
When a producer has a real mode split, set data.view.supported_modes to the
available modes. When the field is absent or contains a single value, consumers
must treat the topology as mode-invariant and avoid showing a mode toggle.
The Cloud topology aggregator consumes detailed payloads when a producer supports the mode, even when the user asked Cloud for aggregated output. The aggregator correlates first, then aggregates the returned graph. Producers must therefore keep detailed mode lossless enough for cross-node matching.
All large arrays use the same compact table shape:
{
"rows": 3,
"columns": [
{"id": "type", "type": "string_ref", "dictionary": "strings"},
{"id": "socket_count", "type": "uint", "aggregation": "sum"}
],
"values": [
{"codec": "dict", "values": [0, 1], "indexes": [0, 0, 1]},
{"codec": "values", "values": [10, 4, 19]}
]
}
columns and values are parallel arrays. The Nth value encoding belongs to
the Nth column. Every decoded column must produce exactly rows values.
Supported codecs:
const: one value repeated for all rows;values: one value per row;dict: a per-column value dictionary plus integer indexes.Column values may be plain scalars or references into a global dictionary.
For string-heavy columns, prefer string_ref with dictionary: "strings" and
integer values. This is how production socket evidence reached about 22 raw
bytes per socket evidence row in the measured corpus.
Use column type json only for actor/custom detail cells that must preserve
nested producer-owned data, such as SNMP port neighbors or vlans. Do not
use json for high-cardinality relationship evidence when a typed scalar,
reference, or array column can carry the fact.
Go topology producers should use src/go/pkg/topology/v1 for the
netdata.topology.v1 response model and compact-table constructors. The helper
validates row counts, parallel columns/values lengths, dictionary indexes,
and JSON round-trip behavior before a producer response reaches the Function
transport.
The actors table must carry enough canonical identity for aggregation. Actor
row indexes are used as link endpoints inside the same payload.
Required actor columns:
type;layer;types.actor_types.<type>.identity.Common actor identity columns:
node_idmachine_guidhostnameipmacprocess_namepidcontainer_idcontainer_namepodnamespacek8s_label:<name>vsphere_moidvsphere_inventory_pathActor types define source-local identity and cross-source merge identity:
{
"types": {
"actor_types": {
"process": {
"layer": "process",
"identity": ["node_id", "process_name"],
"merge_identity": ["node_id", "process_name"],
"parent_identity": ["node_id"],
"aggregation_scopes": ["node", "process_name", "container", "k8s_workload"],
"search": {
"enabled": true,
"columns": ["display_name", "process_name"],
"label_keys": ["cmdline", "username"]
},
"presentation": {
"label": "Process",
"role": "actor",
"icon": "process",
"color_slot": "primary",
"border": {"enabled": true},
"size": {"mode": "link_count", "scale": "normal"},
"layout": {"repulsion": "normal"},
"label_policy": {
"columns": ["display_name", "process_name"],
"fallback": "type_label",
"max_length": 80,
"array": "reject"
},
"ports": {
"show_bullets": true,
"sources": [
{
"source": "actor_table",
"table": "socket_ports",
"actor_column": "actor",
"name_column": "port",
"value_column": "socket_count",
"default_type": "topology"
}
]
}
}
}
}
}
}
identity is what makes an actor unique in the producer payload.
merge_identity is what the Cloud aggregator may use across payloads.
parent_identity expresses containment or "lives on" relationships.
Do not use display names as identity. Identity is canonical matching data, not
UI text. If an identity column is not explicitly listed in the actor type
presentation.label_policy.columns, the UI must not use it as a label
fallback.
Actor type search declares exactly what the graph search bar may index for
that actor type. search.columns[] references actor-table scalar columns.
search.label_keys[] references values in the actor label table, normally
actor_labels.key. Set search.enabled: false for helper actors that should
not appear in graph search, such as synthetic segment or grouping actors. The
UI must not traverse producer-specific details, match, attributes, or
labels paths when rendering a v1 payload.
The links table is the renderable graph projection. It must contain:
src_actor: actor row index;dst_actor: actor row index;type: link type id;evidence_count or socket_count.Link rows are not required to be one-to-one with raw observations. They should usually be grouped by graph identity. Evidence rows preserve the details.
Link types define direction and aggregation semantics:
{
"types": {
"link_types": {
"socket": {
"orientation": "directed",
"direction_role": "flow",
"semantic_role": "traffic",
"aggregation": {
"direction": "preserve",
"evidence": "append",
"metrics": {
"socket_count": "sum",
"rtt_ms_max": "max"
}
},
"evidence_types": ["socket"],
"presentation": {
"label": "Socket",
"color_slot": "primary",
"line_style": "solid",
"width": "normal",
"curve": "auto",
"arrow": "forward",
"layout": {
"strength": "normal",
"distance": "normal"
},
"variable": {
"channel": "width",
"scale_key": "sockets",
"value_column": "socket_count",
"min": "normal",
"max": "emphasis"
}
}
},
"l2_adjacency": {
"orientation": "undirected",
"direction_role": "observation",
"aggregation": {
"direction": "canonicalize_unordered",
"evidence": "append"
},
"evidence_types": ["snmp_l2_observation"]
}
}
}
}
Direction rules:
directed links preserve source and destination order;undirected links may be canonicalized by sorting endpoints;hierarchical links express ownership or containment;observed_bidirectional links preserve observation completeness without
implying traffic direction.direction_role tells the aggregator and UI what direction means:
flow: traffic or socket direction;dependency: logical dependency direction;ownership: parent to child or owner to owned;observation: discovery direction only;none: direction is not meaningful.When types.link_types.<id>.presentation.arrow is auto or omitted, the UI
derives arrows from orientation and direction_role:
undirected -> no arrow;observed_bidirectional -> no arrow;direction_role: none -> no arrow;direction_role: observation -> no arrow;directed with flow or dependency -> forward from src_actor to
dst_actor;hierarchical with ownership -> forward from src_actor to dst_actor;observed_bidirectional does not mean draw arrows at both ends. Producers that
need reverse or both arrows must set presentation.arrow explicitly.
direction_role is required. A link type with orientation: "directed" and no
direction_role is invalid v1 and must not get an inferred arrow from auto.
Declare direction_role: "flow" or direction_role: "dependency" for directed
links that should infer a forward arrow, or set presentation.arrow
explicitly.
For valid values, the auto diagnostic boundary is:
directed+flow, directed+dependency,
hierarchical+ownership, undirected+none, undirected+observation,
observed_bidirectional+none, observed_bidirectional+observation;directed+none, directed+observation, directed+ownership,
hierarchical+none, hierarchical+flow, hierarchical+dependency,
hierarchical+observation, undirected+flow, undirected+dependency,
undirected+ownership, observed_bidirectional+flow,
observed_bidirectional+dependency, observed_bidirectional+ownership.semantic_role is an optional topology-agnostic link classification used by
the UI and aggregator when behavior is not just visual:
normal: ordinary relationship;discovery: discovery-protocol relationship such as LLDP/CDP;ownership: graph-coherence or containment relationship;traffic: traffic, socket, or data-path relationship;correlation: visible correlation or partial-correlation relationship;control: control-plane relationship.Do not infer this role from link type names, protocols, or labels. If a link needs discovery filtering, ownership treatment, traffic emphasis, or correlation treatment, the producer must declare the semantic role explicitly.
Presentation is compact and backend-controlled, but it is not raw frontend layout. The UI owns token meanings; producers only choose from documented tokens.
Type definitions carry their own presentation:
The graph-level data.presentation object carries definitions that are not
owned by one type: legend order, actor-click highlight behavior, port tooltip
field labels, and scale-key labels.
Graph-level and type-level presentation are complementary. If both exist, there
is no precedence rule to apply: type-level presentation styles actor/link/port
types, while data.presentation describes cross-type behavior.
Example:
{
"types": {
"port_types": {
"topology": {
"presentation": {
"label": "Socket",
"color_slot": "primary",
"opacity": "normal"
}
}
}
},
"presentation": {
"profile_version": "network-connections.v1",
"selection": {
"actor_click": {
"mode": "highlight_connections"
}
},
"legend": {
"actors": [
{"type": "process", "label": "Process"}
],
"links": [
{"type": "socket", "label": "Socket"}
],
"ports": [
{"type": "topology", "label": "Socket"}
]
},
"port_fields": [
{"key": "type", "label": "Type"}
],
"scale_keys": {
"sockets": {
"label": "Sockets",
"unit": "count"
}
}
}
}
Safe label policy is mandatory for polished aggregated graphs. It prevents
canonical identity arrays such as many MAC addresses from becoming actor names.
Use only human-safe scalar columns in label_policy.columns; set
fallback: "type_label" unless showing row numbers is explicitly useful.
Link variable scaling is intentionally raw. The producer gives one numeric
value_column and one scale_key; Cloud or the UI scales visible links that
share the same key. min and max are visual tokens for the selected channel:
width scaling uses width tokens and opacity scaling uses opacity tokens. Do not
pre-scale to pixels or opacity in the producer.
Link layout is also tokenized. Producers may set
types.link_types.<id>.presentation.layout.strength and .distance for any
link type. These are relative hints for the UI force layout, not raw physics
values. The allowed strength tokens are weakest, weaker, normal,
stronger, and strongest. The allowed distance tokens are closest,
closer, normal, farther, and farthest.
Current Netdata topology tuning keeps strength at normal and varies only
distance where a topology needs semantic separation. Do not use non-normal
strength tokens for graph polish unless a later product decision explicitly
re-enables force-strength tuning.
Actor layout is tokenized separately from link layout. Producers may set
types.actor_types.<id>.presentation.layout.repulsion to weakest, weaker,
normal, stronger, or strongest. This controls the relative separation of
actors of that type in the UI force graph. It is not interchangeable with link
strength: actor repulsion pushes nodes apart, while link strength pulls link
endpoints together. Producers must not emit raw charge or force numbers.
Initial UI-owned mappings are weakest=-200, weaker=-300, normal=-450,
stronger=-700, and strongest=-1000; these numbers are not schema and may be
tuned after visual QA.
Actor size supports:
fixed: no data-driven size changes;link_count: size may reflect graph degree;metric: size comes from a numeric actor row column named by
metric_column.Actor size may also set scale: "compact", "normal", or "emphasized" for
type-level fixed visual emphasis. Use this for deliberate distinctions such as
self/current node emphasis. Do not force the UI to infer emphasis from actor
type names or labels. Initial UI-owned mappings are compact=0.85,
normal=1.0, and emphasized=1.18; these numbers are not schema and may be
tuned after visual QA. size.scale composes with size.mode; it does not
override data-driven sizing.
When selection.actor_click.mode is highlight_path, the payload must also
set path_table, path_actor_column, and path_order_column. The path table
is an actor detail table. The actor column must contain path-member actor_ref
values and the order column must be numeric so the UI can render the path
deterministically. If the table carries paths for multiple clicked actors, also
set path_owner_column to an actor_ref column that identifies the actor whose
click should use that row.
Port bullets are graph presentation, not modal/table composition. Producers
must define ports.sources[] for actor types that set show_bullets: true.
Each source says where bullets come from:
links: read from the graph links table;evidence: read from a named evidence type or evidence section;actor_table: read from a named actor detail table when present.Each source must define actor_column and name_column. Optional
value_column, type_column, status_column, mode_column, role_column,
and sources_column enrich bullet multiplicity, color, and tooltip fields.
value_column must reference a numeric source-table column; the UI sums it per
bullet key and uses the sum for visible bullet count, overflow, and sizing. Use
it when an aggregated row represents several observations, such as several
sockets on the same process port. default_type must reference
types.port_types and is used when type_column is absent or empty.
name_column must reference a scalar display column, not raw actor/link/
evidence references, arrays, or JSON cells. For evidence sources, evidence
names an evidence type id.
hover.fields is intentionally lightweight graph hover metadata. Full modal
and table composition lives in types.actor_types.<id>.presentation.modal,
types.link_types.<id>.presentation.modal, and optional
types.table_types.<id>.presentation.
annotation is a small actor marker such as a ring or dot. It must use closed
color slots and style tokens; it is not a free-form badge or CSS hook.
Do not emit raw SVG icons. Use the closed UI icon token vocabulary and request a new UI token when a producer needs a new visual concept.
Color slots:
primary, secondary, accent, self, neutral, muted, dim, derived,
info, structural, warning, success, danger, blue, green,
orange, purple, cyan, yellow, teal, gray.
Prefer semantic slots such as primary, warning, derived, or structural
when they fit. Use hue slots only when a topology needs distinct categories that
do not map to the semantic slots, such as the vSphere migration.
Opacity tokens:
normal, muted, faded.
Width tokens:
thin, normal, thick, emphasis.
Icon tokens:
router, switch, firewall, access_point, server, storage,
load_balancer, printer, phone, ups, camera, process, agent,
netdata-agent, parent, remote-endpoint, local-endpoint, segment,
self, ip, cloud, container, docker, kubernetes, lxc, nspawn,
podman, systemd, user, vm, database, service, datacenter,
cluster, host, network, datastore, datastore_cluster,
resource_pool, device, endpoint, correlation, interface, group,
unknown.
Producers must not emit tokens outside the schema. The UI should render unsupported tokens with a safe fallback and record diagnostics for version skew. Producers must not emit raw SVG icons or ask the UI to infer icons from capability strings. If a topology needs a new visual concept, add a closed icon token to the schema and UI token map first.
Evidence sections are keyed by evidence type. Each evidence row must reference
the graph link it supports using a link_ref column.
Example socket evidence type:
{
"types": {
"evidence_types": {
"socket": {
"link_type": "socket",
"role": "relationship_evidence",
"match_columns": [
"client_ip",
"client_port",
"server_ip",
"server_port",
"protocol"
],
"columns": [
{"id": "link", "type": "link_ref", "role": "reference"},
{"id": "src_actor", "type": "actor_ref", "role": "reference"},
{"id": "dst_actor", "type": "actor_ref", "role": "reference"},
{
"id": "client_ip",
"type": "ip_ref",
"dictionary": "strings",
"role": "group_key"
},
{"id": "client_port", "type": "uint", "role": "group_key"},
{
"id": "server_ip",
"type": "ip_ref",
"dictionary": "strings",
"role": "group_key"
},
{"id": "server_port", "type": "uint", "role": "group_key"},
{
"id": "protocol",
"type": "string_ref",
"dictionary": "strings",
"role": "group_key"
},
{"id": "namespace", "type": "string_ref", "dictionary": "strings"},
{"id": "rtt_ms_max", "type": "float", "unit": "ms", "aggregation": "max"}
]
}
}
}
}
Evidence rows are the lossless plane for topology relationships. If Cloud must cross-match sockets across nodes, every socket evidence row must remain available. The graph link can be highly aggregated while the evidence rows stay one-per-observation.
Detail tables support actor modals and drilldowns.
There are four roles:
relationship_evidence: exact relationship rows, usually backed by an
evidence section;relationship_summary: aggregated relationship rows derived from evidence;actor_detail: actor-owned custom data that is not generally aggregatable;actor_inventory: actor-owned inventory data that may be appended or set
merged.Streaming stream_path is actor detail. It must not be treated as a link
evidence table unless each row is also a relationship proof.
Some actor-detail data is intentionally producer-specific and not generally
aggregatable. Use table role actor_detail with aggregation append or
none; use column type json only for cells that need nested objects or
arrays that cannot be represented as scalar columns.
Example actor custom table type:
{
"types": {
"table_types": {
"stream_path": {
"role": "actor_detail",
"owner": "actor",
"aggregation": "append",
"columns": [
{"id": "actor", "type": "actor_ref", "role": "reference"},
{"id": "hop", "type": "uint"},
{"id": "node_id", "type": "string_ref", "dictionary": "strings"},
{"id": "since", "type": "timestamp"}
]
}
}
}
}
Do not duplicate evidence in actor-owned tables. If a modal needs a socket list for an actor, derive it from socket evidence by filtering evidence rows whose link touches that actor.
Actor modals are composed from existing topology facts. They do not get a second copy of actor attributes, socket rows, SNMP endpoint objects, or relationship evidence only for display.
Every actor modal has four top-level entities:
presentation.label_policy;actor_labels table when labels exist;Use a compact actor-owned label table for display labels and metadata:
{
"types": {
"table_types": {
"actor_labels": {
"role": "actor_inventory",
"owner": "actor",
"aggregation": "set",
"columns": [
{"id": "actor", "type": "actor_ref", "role": "reference"},
{"id": "key", "type": "string_ref", "dictionary": "strings"},
{"id": "value", "type": "string_ref", "dictionary": "strings"},
{
"id": "source",
"type": "string_ref",
"dictionary": "strings",
"nullable": true
},
{
"id": "kind",
"type": "string_ref",
"dictionary": "strings",
"nullable": true
},
{"id": "value_index", "type": "uint", "nullable": true}
]
}
}
},
"tables": {
"actor": {
"actor_labels": {
"type": "actor_labels",
"table": {"rows": 0, "columns": [], "values": []}
}
}
}
}
The key, value, source, and kind columns may use either string or
string_ref encoding. Producers should prefer string_ref when a local
dictionary is already used, but aggregators and UI adapters must treat both
encodings as the same logical label fields.
Host/node actors should expose the complete host label set when available.
Non-node actors should expose all useful producer-known labels and metadata,
such as process command line, user, group, namespace, interface role, or
virtualization object properties. If a fact is needed for identity,
correlation, grouping, sorting, filtering, or aggregation, keep it as a typed
canonical actor/evidence/detail column too; actor_labels is not a replacement
for canonical data.
Repeated label values are repeated rows with the same actor and key,
ordered by value_index. Do not encode repeated labels as raw JSON arrays for
normal modal display.
actor_labels inherits the topology Function sensitive-data classification.
Labels may include command lines, users, host labels, system contact/location
fields, or other operator-controlled metadata. Cloud services, aggregators, and
UI adapters that consume topology payloads must apply the same access-control
assumptions as the source Function.
Use modal.labels.identification.fields[] to select the small ordered subset
of label keys that belongs in the actor modal identification/header area. This
selection references the existing actor_labels table; it must not duplicate
values into a separate modal-only table. Missing selected keys are skipped, and
the full Labels tab remains complete.
Modal sections are recipes over existing tables:
{
"types": {
"actor_types": {
"process": {
"layer": "process",
"identity": ["node_id", "process_name"],
"presentation": {
"label": "Process",
"label_policy": {
"columns": ["display_name", "process_name"],
"fallback": "type_label",
"array": "reject"
},
"modal": {
"labels": {
"enabled": true,
"table": "actor_labels",
"identification": {
"fields": [
{"key": "process", "label": "Process", "max_values": 1},
{"key": "username", "label": "User", "max_values": 1}
]
}
},
"mini_topology": {
"enabled": true,
"depth": 1,
"exclude_link_types": ["ownership"]
},
"sections": [
{
"id": "connections",
"label": "Connections",
"source": {"kind": "links"},
"owner_filter": {
"mode": "incident_link",
"src_actor_column": "src_actor",
"dst_actor_column": "dst_actor"
},
"row_filters": [
{"column": "type", "op": "not_in", "values": ["ownership"]}
],
"columns": [
{
"id": "remote",
"label": "Remote",
"projection": {
"kind": "opposite_actor",
"src_actor_column": "src_actor",
"dst_actor_column": "dst_actor"
},
"cell": "actor_link"
},
{
"id": "protocol",
"label": "Protocol",
"projection": {"kind": "direct", "column": "protocol"},
"cell": "badge"
},
{
"id": "sockets",
"label": "Sockets",
"projection": {"kind": "direct", "column": "socket_count"},
"cell": "number"
}
]
}
]
}
}
}
}
}
}
Table recipes must support these source kinds:
actors;links;evidence with an evidence id;actor_table with a table id;relationship_table with a table id.Use owner_filter to bind rows to the selected actor or link. Common filters
are actor_column, link_column, incident_link, incident_evidence, and
selected_link.
Use projections instead of duplicated display fields:
direct: read a column from the source row;actor_ref_label: render an actor-ref column through actor label policy;opposite_actor: render the opposite endpoint of a link row;formatted_endpoint: combine IP, port, and optional protocol columns;selected_side_endpoint: choose local or remote endpoint fields based on
whether the selected actor matches the source or destination actor columns;
producers must provide src_actor_column and dst_actor_column plus at
least one local endpoint column and one remote endpoint column;label_lookup: read a value from actor_labels by label_key; omit
actor_column to look up labels for the selected modal actor, or provide an
actor-ref source column when the lookup belongs to another actor in the row;coalesce: choose the first non-empty column;json_path: extract a declared scalar path from a declared JSON column;const: emit a fixed value.Use cell types to keep rendering generic and polished:
text;number;badge;actor_link;timestamp;duration;endpoint;array_count;debug_json.Use visibility annotations instead of duplicating rows:
table: shown in the normal table;expanded: hidden from the main grid but shown when the row expands;hidden: available for joins, sorting, or future use, not displayed;debug: diagnostic-only. Raw JSON belongs here unless a curated scalar
projection exists.json columns are allowed only when they preserve nested producer-owned facts
that the UI or aggregator understands through declared projections, or when
they are explicitly marked as debug_json. They are not acceptable as the
normal user-facing rendering for labels, endpoint objects, neighbor arrays, or
actor attributes.
Use empty_label for the section-level empty-state label. Use column
badge_map only for explicit value-to-token mapping, and keep align and
sortable as presentation hints over already-projected columns. These fields
must not add new data or embed UI components.
The Cloud aggregator should preserve and merge modal/table definitions by the same namespace/deduplicate rules used for type presentation. It should not materialize modal rows during aggregation unless it is already merging the underlying canonical table.
Topology should be refreshable without recomputing topology. Overlay templates define how the UI or Cloud can query metrics for an actor or link.
Templates live once in the type registry. Overlay refs carry only template ids, one owner reference, and selector parameters.
For provider: "netdata.metrics", selector params are interpreted by the
consumer:
node_id scopes the metric query to a node;collect_job maps to the chart label _collect_job;Example:
{
"types": {
"overlay_templates": {
"snmp_interface_traffic": {
"provider": "netdata.metrics",
"contexts": ["snmp.interface_traffic"],
"dimensions": ["received", "sent"],
"selector_params": ["node_id", "if_name"],
"merge": {
"refs": "set",
"values": "sum"
}
}
}
},
"overlays": {
"refs": {
"rows": 1,
"columns": [
{"id": "template", "type": "string_ref", "dictionary": "strings"},
{"id": "link", "type": "link_ref", "role": "reference"},
{"id": "node_id", "type": "string_ref", "dictionary": "strings"},
{"id": "if_name", "type": "string_ref", "dictionary": "strings"}
],
"values": [
{"codec": "const", "value": 0},
{"codec": "const", "value": 0},
{"codec": "const", "value": 1},
{"codec": "const", "value": 2}
]
}
}
}
Overlay refs use schema ids for column names, so selector params and ref column
ids must start with a letter and contain only letters, digits, _, ., :,
or -. The template column identifies the template by name. Exactly one
convention owner column must be present: actor with type actor_ref or link
with type link_ref. No other actor_ref or link_ref columns are valid in
overlay refs. Every row must have a non-null value in the owner column. Each
selector param used by a referenced template must have a same-named refs-table
column.
Selector params must not use the reserved refs-table convention column names
template, actor, or link.
The template column and selector-param columns required by a row's resolved
template must be string or string_ref. Required selector-param values must
resolve to non-empty strings. Selector-param columns used by other templates may
be nullable and null on rows whose template does not require them.
Overlay refs are optional. Do not fabricate per-link bandwidth if the producer does not have it. For network sockets, current evidence may include snapshot metrics such as RTT or retransmissions, but that is not a time-series overlay unless a query provider exists.
Aggregation must be schema-driven:
Common column rules:
set: preserve unique values;sum: add counters;min / max: preserve bounds;last / first: pick by observation order;count: count rows;none: not aggregatable.If a table or column has no valid aggregation, mark it none and keep rows
attached to their owner. Do not silently drop rows.
topology:network-connections supports three actor grouping levels:
group_by:process_name: one process actor per process name.group_by:pid: one process actor per PID. This is the only mode that may
emit raw per-PID fields.group_by:container: one container actor per canonical container_name.
For systemd services, container_name is the service name. For
user.slice/user-UID.slice, network-connections uses a composed user actor
only when cgroups did not return a usable container name; the actor name is
the process UID's resolved username, or user${UID} if username resolution is
unavailable. Known rootless Docker and other known cgroups with a container
label or cgroup name keep their cgroups-provided container identity. For
non-container, non-service processes, it falls back to process name.The actor columns are:
| Column | Role | Source |
|---|---|---|
process | group key | process name; populated for process_name and pid modes |
pid | identity | host PID; populated only for group_by:pid |
container_name | group key | cgroups-provided container name or cgroup name when known; otherwise user-slice username, systemd unit, then process fallback |
cgroup_path | attribute | APPS_LOOKUP cgroup path; populated only for group_by:pid |
cgroup_name | attribute | APPS_LOOKUP cgroup name; populated only for group_by:pid |
orchestrator | attribute | rendered from APPS_LOOKUP cgroup status and orchestrator enum; populated only for group_by:pid |
k8s_pod_name | attribute | k8s_pod_name label; populated only for group_by:pid |
k8s_namespace | attribute | k8s_namespace label; populated only for group_by:pid |
k8s_workload | attribute | k8s_controller_name, or pod-name suffix stripping; populated only for group_by:pid |
docker_container_name | attribute | k8s_container_name, container_name, then cgroup_name; populated only for group_by:pid |
docker_image | attribute | image label; populated only for group_by:pid |
systemd_unit_name | attribute | systemd unit component from cgroup_path; populated only for group_by:pid |
The actor types advertise these aggregation scopes:
process_name -> processpid -> pid, net_ns_inodecontainer -> container_nameview.group_by lists exactly process_name, pid, and container.
view.scope is the active group selected for the Function call.
The orchestrator column values are systemd, docker, k8s, kvm,
lxc, podman, nspawn, host_root, and unknown. host_root is a
producer-local rendering of APPS_LOOKUP cgroup_status == HOST_ROOT; it is
not a netipc orchestrator enum value. These values are raw PID-mode attributes,
not standalone topology groupings in the current contract.
Free-form cgroup labels are not emitted by default. Operators opt in per
Function call with labels:<pattern>, where pattern is pipe-separated and
matched against label keys with simple_pattern semantics, for example
labels:team|app|version-*. Commas are literal characters. The whitelist is
applied only at Function-output emission; upstream caches and IPC payloads
carry the raw labels. Raw cgroup paths are emitted only in group_by:pid.
Correlation is producer-visible graph semantics, not aggregator state. Producers declare how independently produced topology maps can be correlated by keys, but they do not expose internal aggregator states such as candidate, absorbed, rewrite plan, or equivalence class in final payloads.
Correlation can resolve several shapes:
Use data.correlation when a topology can resolve correlation actors across
payloads:
rules defines named correlation rules, priority, key space, key template,
rule class, action, point actor types when visible points exist, optional
claim actor types, correlation link types, and the final output link type;points is a compact table of visible correlation actors and their keys;claims is a compact table of real actors and the keys they satisfy.Correlation keys are declarative. A key is built from table columns and string literals. The aggregator normalizes values by column type and concatenates the parts; it does not execute code and does not need to know what IP, port, MAC, chassis id, vSphere MOID, or another domain value means.
Supported rule classes:
resolve_loose_side: resolve a loose relationship side or visible
correlation actor to a claim actor when the key is unambiguous;replace_actor: remove weaker placeholder actors and rewire incident
relationships to stronger managed actors;merge_enrich_actor: merge actors with the same declared identity and merge
their labels, attributes, evidence, and detail tables by table policy.Supported rule actions remain the visible output behavior:
absorb: on an exact unambiguous match, matching correlation actors are
removed from the aggregated output, or loose-side placeholders are consumed,
and incident correlation relationships are rewired to the matched real actor
using output_link_type;link: on an unambiguous broader/partial match, the correlation actor remains
visible, or a materialized partial actor remains visible, and a weak
correlation link to the matched actor is emitted using output_link_type.No match keeps the correlation actor visible. Ambiguous matches must stay unresolved and produce diagnostics in the aggregator; producers must not encode guessing policy in the schema.
NAT or other alias evidence can be represented by adding more point or claim rows for the same actor and rule. The original key remains intact; aliases are additional facts, not mutations of the source observation.
Links between real actors and visible correlation actors must use semantic correlation link types even in a single-node topology. The UI and aggregator must not infer correlation behavior from actor type names or topology kind. The legend should include visible correlation actor and link types so users can distinguish unresolved, partial, inferred, and resolved relationships.
For network-connections, graph links must be split into semantic families:
Socket evidence preserves exact tuples.
Canonical socket evidence columns:
Do not emit these per socket:
port_name if it can be derived from port;In the measured Cloud corpus, this production-only socket evidence shape was about 7.25 MB raw for 323,077 socket evidence rows, or about 11.25 MB raw when including current RTT/retransmission metrics.
topology:network-connections has a fixed producer-side response budget of
64 MiB. If a live request would exceed that budget, return a Function error with
HTTP status 413; do not emit a truncated topology and do not add
partial-topology metadata unless a future product/schema decision introduces that
contract.
Network-connections graph direction is dependency direction: link types use
direction_role: "dependency", the client actor is always src_actor, and the
server actor is always dst_actor. The topology payload must not expose a
separate local direction. Local host or same-node sockets still resolve to
either inbound or outbound dependency direction, and same-node process pairs
should emit resolved process-to-process links when both actors are known.
For outbound sockets, the process claims the client protocol + client_ip + client_port tuple and the correlation endpoint points at the server protocol + server_ip + server_port tuple. For inbound sockets, the process claims the
server tuple and the correlation endpoint points at the client tuple. Listening
sockets have no remote correlation point.
The current socket_exact rule key is protocol + address_space + ip + port;
address_space prevents private or otherwise scoped endpoint identities from
matching across unrelated address domains.
Network-connections uses four link types with different graph semantics:
endpoint_socket: process-to-correlation-endpoint links. These are the
primary unresolved network dependencies in a single-node view. They should be
solid, colored, thin, normal-strength, and normal-distance so unresolved
endpoints do not force the graph to zoom out.correlated_socket: aggregator output after exact endpoint absorption. These
are cross-payload process-to-process dependencies and should be
normal-strength and farthest so independent topology clusters do not blend
into one dense layout.socket: resolved local process-to-process socket links. These are gray,
thin, normal-distance links whose width can vary by socket_count.ownership: node-to-process containment links. These are graph-coherence
links, not network traffic. They should be dotted, faded/dim, thin, normal
strength, and normal distance.Aggregated network-connections payloads still need process port bullets without
shipping detailed socket evidence. Emit an actor_inventory table such as
socket_ports with actor, port, and numeric socket_count, then point the
process actor type's presentation.ports.sources[] at that actor table with
value_column: "socket_count". Size process actors with
presentation.size: {"mode": "metric", "metric_column": "socket_count"}.
Network-connections actor modals should expose dependency semantics, not every internal table as a peer tab:
Processes over links, filtered to type == ownership;Dependencies and Dependants over
tables.relationship.connections;Dependencies and Dependants over
evidence.socket;Dependencies filters rows where the selected actor is src_actor;Dependants filters rows where the selected actor is dst_actor.Do not show socket_ports as a normal network-connections modal section. It is
only the graph port-bullet inventory. Put less common per-connection fields such
as retransmissions or receiver RTT behind visibility: "expanded" instead of
creating another duplicate tab.
For streaming:
stream_path is an actor-detail table, not relationship evidence unless a
row proves a specific relationship.Streaming custom tables are allowed because they carry actor-owned state that cannot be derived from links.
Streaming parent actor size should use the actor row retained_node_count
metric: presentation.size: {"mode":"metric","metric_column":"retained_node_count"}. Do not use generic
graph degree or direct child count for parent sizing; the operational question
is how many nodes have data retained by the parent, including self, virtual
nodes, stale nodes, and transit descendants when they have DB retention state.
Parent graph bullets should be derived from incoming streaming links by using a
ports.sources[] entry over links with actor_column: "dst_actor" and a
scalar child/node display column such as port_name.
Streaming modal identification should be role-specific. Host-like actors
(parent, child, and stale) should use compact status plus OS/hardware/
platform labels from actor_labels: hostname, node type, health, stream,
ingest, OS, OS version, kernel, architecture, CPU, cores, RAM, virtualization,
container, cloud placement, and Agent version. Parents also include retained
nodes and direct children. Vnode actors should use inventory labels such as
vnode type, vendor, model, address, location, sys object id, LLDP name, and
status. Keep long identifiers such as machine GUID and node id available in the
full Labels tab instead of promoting them into the header.
Streaming detail tables can contain stable node or Cloud identifiers. These fields must not be used as graph labels and must not be copied into logs, diagnostics, docs, SOWs, or durable review artifacts without redaction.
For SNMP/L2:
undirected or observed_bidirectional;Managed SNMP device actor modals are port-centric:
modal.labels.identification.fields[] to show key device labels such as
device name, management IP, vendor, model, port counts, and LLDP/CDP counts
in the modal identification area;actor_ports as the primary Ports section;if_index as the visible
numeric port ID when known, source port_id, display name, if_name,
if_descr, if_alias, MAC, speed, status, mode, role, VLAN, FDB, link, and
neighbor counts;if_index must come from the device/SNMP facts;neighbor_actor and neighbor_port_name when graph-link facts can align the
port to a remote actor;actor_port_links as the Port Neighbors section when device modal
rows need remote actor/port/evidence details;Links
section when they do not own port inventory.actor_port_links is an actor-owned modal index over existing graph links and
evidence. It may duplicate compact references and side-specific port facts so
the UI can show a device's local port next to its remote actor, but it must not
duplicate raw LLDP/CDP/FDB/ARP/STP evidence JSON. Every row should include the
selected actor, link reference, remote actor, local if_index, local port name,
remote port facts, protocol, link type, state, evidence count, confidence,
inference, attachment mode, and timestamps when known.
SNMP endpoint port names must come only from real port fields: port_name,
if_name, if_descr, or source port_id. Do not fall back to actor labels
such as display_name or sys_name; those belong in actor labels, not in
local/remote port cells.
SNMP interface traffic, packets, errors, and state should use overlay templates with compact refs to node/context/label selectors.
For vSphere:
hierarchical with direction_role: "ownership";The vSphere producer in the separate worktree must be updated in place only after coordination with the user because another agent may be editing it.
Compatibility tests may project the new canonical payload into rollout adapter shapes to prove that no information needed by compatibility consumers was lost.
That projection code must live only in tests or local schema-lab tools. It may hardcode adapter presentation fields, display strings, modal table shapes, and compatibility object layouts. None of that belongs in production payloads.
The acceptance check is:
Do not add fields to production payloads solely to make this projection easier.
Before shipping a topology producer:
actor_labels table when
available;actor_labels, while
keeping identity, correlation, grouping, sorting, filtering, and aggregation
facts as typed canonical columns;show_port_bullets to
types.actor_types.<id>.presentation.ports.show_bullets and define
ports.sources[] so bullet data is not implicit;src/go/pkg/topology/v1 helpers for Go producers;.local/ only.