SkeleTrace

Data formats

SkeleTrace works in plain JSON. This page describes each file it writes and each file it reads, field by field, so its output can be checked, archived and processed by other tools.

Conventions

  • Encoding. Files are UTF-8; a byte-order mark is tolerated. The viewer also reads UTF-16, which is what Windows PowerShell writes when output is redirected with >.
  • Identifiers. Entity identifiers are UUIDs written as strings. Identifiers in case graphs are derived from the case itself, so the same case always produces the same graph.
  • Times. Times are RFC 3339 strings with a time zone, such as 2026-09-30T21:00:00Z or 2026-09-30T22:00:00+01:00.
  • Coordinates. Positions are WGS 84 degrees, latitude from −90 to 90 and longitude from −180 to 180. GeoJSON coordinates are written longitude first, as RFC 7946 requires.
  • Names of things. Kinds and modes are written as their names, such as "Observer" or "Geodesic". The order of keys inside an object carries no meaning.

This page covers the files SkeleTrace writes, and the files you prepare for the case notebook and the landscape. The engine’s own configuration (profiles, view jobs, filters, saved queries and watchlists) is outside its scope.

Command output

Every skeletrace command prints a single JSON document. Results are wrapped in a named envelope that says what they are, so the viewer and any script can unwrap them by name.

CommandEnvelopeContents
materialize-topology{"Topology": …}Topology view
case graph{"Topology": …}Topology view of a case
materialize-sparse-geo{"SparseGeo": …}Sparse-geo map
analyze-topology{"TopologyAnalysis": …}Bottleneck report
case bottlenecks{"Case": {"Bottlenecks": …}}Bottleneck report
case show{"Case": {"Document": …}}Case document
case export --format jsonNoneCase document, written to the file named
landscape-report, verify-packets{"Landscape": …}Landscape report

Other case commands answer under Case, with names such as RunSaved, Runs, Observation, Linked and Exported.

Shared building blocks

Position

{ "lat": 51.481, "lon": -3.179, "alt": null }
FieldTypeMeaning
latnumberLatitude in degrees, −90 to 90.
lonnumberLongitude in degrees, −180 to 180.
altnumber or nullAltitude, where known.

Extent

A rectangle on the map, used for boundaries and for the viewport of a sparse-geo map.

{ "min_lat": 35.0, "min_lon": -11.0, "max_lat": 62.0, "max_lon": 30.0 }

Metric value

As it appears in topology views:

{
  "metric_id": "5f0c2a1e-8d3b-4c55-9e1a-2b7c4d6e8f90",
  "metric_name": "latency",
  "display_value": "42.0",
  "unit": "ms",
  "timestamp": "2026-09-30T21:00:00Z"
}

display_value is text, already formatted for reading. timestamp is the time of the sample shown.

Kinds and modes

Node kinds
Endpoint, Hop, Relay, Observer, Anchor, Exchange, Identity, Account, Topic, Cluster, Chokepoint, Unknown
Edge kinds
Route, Link, Inference, Association, Membership, Reference, BoundaryCrossing
Geometry modes
Straight, Geodesic, RaisedArc, ThroughGlobe, Abstract; see place and geometry

Topology view

Written by materialize-topology and case graph.

{
  "Topology": {
    "view_id": "b8e5e04c-13de-55f0-97b3-8f8f46327759",
    "nodes": [
      {
        "entity_id": "4a4c857b-fe5c-5dd7-8c9b-b91f4e5ac455",
        "label": "Cardiff observer",
        "kind_label": "Observer",
        "position": { "lat": 51.481, "lon": -3.179, "alt": null },
        "metrics": []
      },
      {
        "entity_id": "662fc329-bc88-5f6b-ab76-4f34cb036591",
        "label": "London exchange",
        "kind_label": "Exchange",
        "position": { "lat": 51.507, "lon": -0.128, "alt": null },
        "metrics": [
          {
            "metric_id": "5f0c2a1e-8d3b-4c55-9e1a-2b7c4d6e8f90",
            "metric_name": "throughput",
            "display_value": "3.1",
            "unit": "Tbit/s",
            "timestamp": "2026-09-30T21:00:00Z"
          }
        ]
      }
    ],
    "edges": [
      {
        "entity_id": "18af9bb0-d3a4-58e8-9bd6-2b67e6bca3d0",
        "source": "4a4c857b-fe5c-5dd7-8c9b-b91f4e5ac455",
        "target": "662fc329-bc88-5f6b-ab76-4f34cb036591",
        "kind_label": "Route",
        "geometry_mode": "Straight",
        "metrics": []
      }
    ],
    "boundaries": [
      {
        "entity_id": "0d6c1b8e-6a4f-4f4e-9b1e-3c2d5e7f9a10",
        "label": "EU jurisdiction",
        "kind_label": "Legal",
        "extent": { "min_lat": 35.0, "min_lon": -11.0, "max_lat": 62.0, "max_lon": 30.0 },
        "metrics": []
      }
    ]
  }
}
FieldMeaning
view_idIdentifier of the view that produced this file.
nodes[].entity_idThe node’s identifier.
nodes[].labelA human-readable name.
nodes[].kind_labelOne of the node kinds.
nodes[].positionA position, or null when the node has none.
nodes[].metricsLatest metric values for the node.
edges[].source, edges[].targetThe entity_id of each end.
edges[].kind_labelOne of the edge kinds.
edges[].geometry_modeHow the link is drawn on maps.
boundaries[].extentAn extent, or null when the boundary has no geography.

Case graphs use the same shape. Sources appear as Observer nodes, observations of people and organisations as Identity nodes and observations of domains as Endpoint nodes. Each observation has a Reference link to its source, and each reviewed link appears as an Association.

Sparse-geo map

Written by materialize-sparse-geo. The map itself is a standard GeoJSON feature collection, wrapped with the viewport it was drawn for.

{
  "SparseGeo": {
    "viewport": { "min_lat": 30.0, "min_lon": -80.0, "max_lat": 60.0, "max_lon": 10.0 },
    "feature_count": 2,
    "geojson": {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "id": "4a4c857b-fe5c-5dd7-8c9b-b91f4e5ac455",
          "geometry": { "type": "Point", "coordinates": [-3.179, 51.481] },
          "properties": {
            "label": "Cardiff observer",
            "kind": "Observer",
            "metrics": {
              "latency": {
                "metric_id": "5f0c2a1e-8d3b-4c55-9e1a-2b7c4d6e8f90",
                "value": "11",
                "unit": "ms",
                "timestamp": "2026-09-30T21:00:00+00:00"
              }
            }
          }
        },
        {
          "type": "Feature",
          "id": "18af9bb0-d3a4-58e8-9bd6-2b67e6bca3d0",
          "geometry": { "type": "LineString", "coordinates": [[-3.179, 51.481], [-74.006, 40.713]] },
          "properties": { "label": "Transatlantic route", "kind": "Route", "metrics": {} }
        }
      ]
    }
  }
}
  • A feature’s id is the entity’s identifier.
  • Nodes are Point features and routes are LineString features. Extents are written as closed rectangular Polygon features.
  • properties.metrics is an object keyed by metric name; each entry holds metric_id, value, unit and timestamp.
  • The viewer joins a line to the points at its two ends when their coordinates match, and treats it as a link between them; other lines are drawn as tracks.

Bottleneck report

Written by analyze-topology and case bottlenecks. In the example, one group of four is shown.

{
  "Case": {
    "Bottlenecks": {
      "node_count": 20,
      "edge_count": 12,
      "components": [
        [
          "4a4c857b-fe5c-5dd7-8c9b-b91f4e5ac455",
          "662fc329-bc88-5f6b-ab76-4f34cb036591",
          "3028c9b9-bc7a-5166-8700-84e820ac1817",
          "707e8899-14b5-5ef4-8f07-81b4321c70ee"
        ]
      ],
      "chokepoints": [
        {
          "entity_id": "662fc329-bc88-5f6b-ab76-4f34cb036591",
          "label": "#1 Avery Example",
          "kind_label": "Identity",
          "degree": 2,
          "splits_into": 2
        }
      ],
      "bridges": [
        {
          "entity_id": "18af9bb0-d3a4-58e8-9bd6-2b67e6bca3d0",
          "source": "662fc329-bc88-5f6b-ab76-4f34cb036591",
          "target": "4a4c857b-fe5c-5dd7-8c9b-b91f4e5ac455",
          "kind_label": "Reference"
        }
      ],
      "dangling_edges": []
    }
  }
}
FieldMeaning
node_count, edge_countThe size of the view analysed.
componentsGroups of connected nodes, largest first, as lists of entity identifiers.
chokepointsNodes whose removal splits their group, the most disruptive first. degree counts the node’s links; splits_into is the number of pieces its removal leaves.
bridgesLinks whose loss splits their group.
dangling_edgesLinks whose ends are not both in the view. They are left out of the analysis.

A report holds no positions and only some of the links. Opened on its own, the viewer shows what it can; dropped onto the topology view it was made from, the viewer compares the report with its own analysis.

Case document

Written by case show, and without the envelope by case export --format json. The same document is produced by the Python OSINTropy tool. One observation is shown.

{
  "Case": {
    "Document": {
      "id": "b626be92cc674c5b89df55763f8e772c",
      "query": "Avery Example",
      "created_at": "2026-10-01T06:36:03+00:00",
      "status": "complete",
      "errors": {},
      "observations": [
        {
          "id": 1,
          "source": "npi",
          "source_id": "1234567890",
          "name": "Avery Example",
          "fields": { "practice_address": ["1 Practice St, Sampleton, CA, 12345"] },
          "source_url": "https://npiregistry.cms.hhs.gov/provider-view/1234567890",
          "kind": "record",
          "subject_type": "person",
          "collected_at": "2026-10-01T06:36:03+00:00"
        }
      ],
      "reviewed_links": [
        { "a": 1, "b": 2, "reason": "Same NPI number on both records", "created_at": "2026-10-01T06:40:12+00:00" }
      ],
      "analysis": {
        "observation_count": 2,
        "record_count": 2,
        "lead_count": 0,
        "source_counts": { "npi": 1, "wikidata": 1 },
        "source_diversity_bits": 1.0,
        "reviewed_groups": [[1, 2]],
        "possible_reviews": [],
        "field_variations_in_reviewed_groups": [],
        "unreviewed_ids": [],
        "interpretation": "Source diversity measures distribution, not truth, identity, or confidence."
      },
      "method": "Each source hit remains separate until two observation IDs are explicitly linked after review."
    }
  }
}

The run

FieldMeaning
idRun identifier, 32 hexadecimal characters.
queryWhat was searched for or imported.
statuscomplete; partial when some sources failed; failed when none returned anything.
errorsSource name to error message, for the sources that failed.

Observations

FieldMeaning
idObservation number, unique within the case file.
source, source_idThe source, and the identifier the source uses for this entry.
nameThe name as the source gives it. Leads are named after the search, for example Search lead for Avery Example.
fieldsField name to a list of values, exactly as the source gave them.
source_urlThe public address of the entry, or an empty string.
kindrecord, lead or import.
subject_typeperson, organization or domain.
collected_atWhen the observation was collected.

Links and analysis

Each reviewed link names two observations, a and b (the smaller number first), with its reason and the time it was made. The analysis repeats what the run shows: counts of observations, records and leads; observations per source; source diversity in bits; groups formed by reviewed links; the review queue (possible_reviews); fields that disagree within a group; and the observations not yet in any group.

Case imports

Read by case import-file. A JSON import is either a list of observations or an object with a query and an observations list.

{
  "query": "Notes on Example Works Ltd",
  "observations": [
    {
      "source": "companies_register_extract",
      "source_id": "EW-0001",
      "name": "Example Works Ltd",
      "subject_type": "organization",
      "source_url": "https://example.org/register/EW-0001",
      "collected_at": "2026-09-01T10:00:00+01:00",
      "fields": { "company_number": "EW-0001", "city": "Cardiff" }
    }
  ]
}
FieldRequiredRules
sourceYesUp to 100 characters.
nameYesUp to 250 characters.
source_idNoUp to 250 characters. When it is missing, a fingerprint of the source, name, address and fields is used, so the same entry always receives the same identifier.
kindNorecord, lead or import. Defaults to import.
subject_typeNoperson, organization or domain. Defaults to person.
source_urlNoAn http or https address with no user name or password in it.
collected_atNoA time with a time zone. Defaults to the time of import.
fieldsNoUp to 64 names. Each value is text, a number, or a list of up to 100 of them, each up to 4,096 characters. Empty and repeated values are dropped.

All text is collapsed to single spaces. Control characters and the characters that reverse text direction are refused, so that nothing in a case can disguise itself. An import may hold up to 10,000 observations and 20 MB.

A CSV import needs a header row with source and name columns. The columns source_id, source_url, kind, subject_type and collected_at are read as above. Every other column becomes a field, and a fields_json column may carry further fields as a JSON object.

source,name,source_id,subject_type,source_url,collected_at,role,city
staff_directory,Avery Example,SD-17,person,https://example.org/staff/17,2026-09-01T09:30:00+01:00,Director,Cardiff

Landscape files

Report request

Read by landscape-report. Only mode is required; the other values shown are the defaults.

{
  "mode": "replay",
  "input": "captures/events.csv",
  "speed": 1.0,
  "rate": 4096,
  "seed": 221909157,
  "options": {
    "tick_ms": 125,
    "width": 120,
    "height": 36,
    "window": 256,
    "max_alerts": 50,
    "max_ticks": 28800,
    "include_frame": true
  }
}
FieldMeaning
modedemo, replay (CSV or JSONL packet events), bytes (any file) or pcap-file (pcap or pcapng).
inputPath to the input, relative to where the command runs. Required for every mode except demo.
speedPlayback speed for replays and captures, above 0 and up to 100,000.
rateBytes per second fed through the analysis in bytes mode.
seedSeed for the demonstration.
options.tick_msMilliseconds per column, 20 to 5,000.
options.width, options.heightSize of the drawn frame in characters: 20 to 1,000 wide, 8 to 500 high.
options.windowObservations per lane used for the statistics, 32 to 4,096.
options.max_alertsAlerts kept, 1 to 10,000.
options.max_ticksWhere to stop: 480 ticks (one minute) by default for the demonstration, 28,800 for files.
options.include_frameWhether to include the final frame as text.

Verification request

Read by verify-packets: {"input_path": "captures/events.jsonl", "options": { … }}, with the same options as above.

Report

Both commands answer with a report under Landscape:

FieldMeaning
source_label, lane_setWhat was read, and whether the lanes are packets or bytes.
ticks, tick_ms, stream_secondsHow much stream time was covered.
ended, truncated, warming_upWhether the input ran out, whether the run stopped at its limit first, and whether the statistics had too little data to settle.
total_events, total_bytes, bad_records, droppedTotals, including malformed rows that were skipped.
lanesOne entry per lane: totals, the current class and markers, entropy, dependence, entropy rate, rhythm, divergence from the lane’s history, rates, and counts of each column type.
alertsNewest first, each with its stream time, lane, kind (demon, shift, burst or rhythm) and message.
frameThe final frame as plain text, when requested.

Packet event files

Packet events can be CSV with a header row, JSONL with one object per line, or, for verify-packets, a JSON list. The fields are the same in every form: ts_ms, len, proto, src, dst, sport, dport and dir.

# JSONL: lines starting with # are ignored
{"ts_ms": 0, "len": 1500, "proto": "tcp", "src": "10.0.0.2", "dst": "93.184.216.34", "sport": 51000, "dport": 443}
{"ts_ms": 5, "len": 80, "proto": "udp", "sport": 53000, "dport": 53}
  • ts_ms is a whole number of milliseconds. A row without it is placed 10 milliseconds after the row before.
  • len is the packet length in bytes. The protocol and ports decide the lane.
  • Time never runs backwards: a row stamped earlier than one already read is held at the latest time seen.
  • Malformed rows are counted in bad_records and skipped; they do not stop the run.
  • verify-packets loads files of up to 64 MiB. Larger files can be streamed with skeletrace landscape --mode replay.

Other files the viewer opens

GeoJSON
Points become nodes, lines between two points become links, other lines become tracks and polygons become areas. The properties label or name, and kind_label or kind, are used when present.
GraphML
Nodes, edges and their data keys. Case notebooks exported as GraphML open with sources, observations and leads told apart.
Graph JSON
An object with nodes and edges or links, using id, label and kind on nodes and source and target on links. Index references and Cytoscape element lists are accepted.
Observation lists
A JSON list of observations, as used for imports, opens as a case.

A landscape report is not a graph, and the viewer says so rather than guessing.