Edge

data

Edge

Definition

The term "edge" carries two distinct but deeply related meanings in the Prismatic Platform context, both concerning connections and boundaries within larger systems.

In graph theory and graph databases, an edge is a connection between two vertices (nodes) representing a relationship, dependency, or association. Edges can be directed (having a source and target, like "company employs person") or undirected (bidirectional, like "person knows person"), and may carry labels (relationship types), properties (metadata attributes), weights (strength or confidence scores), and temporal bounds (validity periods). In the DD knowledge graph, edges represent the relationships that intelligence analysts traverse to uncover ownership structures, financial flows, and hidden connections between entities.

In infrastructure and networking, edge computing refers to processing and serving data at network boundary locations physically close to end users, reducing latency and improving data locality. Edge locations are the outermost layer of a distributed system's topology, sitting between users and the centralized origin servers. For LiveView applications like Prismatic, where every user interaction travels over a WebSocket to the server and back, edge proximity directly determines perceived responsiveness -- a 100ms round-trip in Frankfurt versus 300ms to US East is the difference between "instant" and "noticeable delay".

Both meanings share a common architectural principle: edges define the connective tissue that gives structure and accessibility to systems, whether those systems are knowledge graphs or geographic networks.

Core Concepts

Graph Edge Classification

Edge TypeDirectionPropertiesExampleDatabase Support
DirectedSource -> TargetAsymmetric relationship"company employs person"KuzuDB, :digraph, Neo4j
UndirectedBidirectionalSymmetric relationship"person knows person"KuzuDB (via two directed edges)
LabeledNamed relationshipType classification:owns, :manages, :related_toKuzuDB, all graph DBs
WeightedNumeric propertyConfidence/strength scoreConfidence: 0.87 on ownership linkKuzuDB, :digraph
TemporalTime-boundedValidity period"CEO from 2020-01 to 2024-06"KuzuDB
Multi-edgeMultiple parallelDifferent relationship typesBoth "owns" and "manages" between same pairKuzuDB
HyperedgeN-ary connectionConnects >2 verticesBoard meeting linking 5 directorsRequires special modeling

Graph Traversal Patterns

PatternDescriptionComplexityDD Use Case
BFSBreadth-first search from a vertexO(V + E)Finding all direct connections of an entity
DFSDepth-first search following pathsO(V + E)Tracing ownership chains to ultimate beneficiary
Shortest PathMinimum hops between two verticesO(V + E) with BFSFinding closest connection between two entities
All PathsEvery path between two verticesExponential worst caseMapping all connection routes in corruption networks
Cycle DetectionFinding circular relationshipsO(V + E)Detecting circular ownership structures
Subgraph MatchingPattern matching on graph structureNP-hard generalFinding known fraud patterns in entity networks
PageRankImportance scoring by incoming edgesO(iterations * E)Identifying key entities in a relationship network
Community DetectionClustering densely connected verticesO(E * log V)Grouping related entities into organizational clusters

Infrastructure Edge Locations

Edge LocationProviderRegionLatency (EU users)Prismatic Usage
Frankfurt (FRA)Fly.ioEU Central< 15msPrimary production, database
Amsterdam (AMS)Fly.ioEU West< 20msSecondary, failover
Warsaw (WAW)Fly.ioEU East< 25msCzech/Polish user optimization
London (LHR)Fly.ioUK< 25msUK compliance endpoints
US East (IAD)Fly.ioAmericas< 80ms from EUMonitoring, US-facing API
CDN (Global)Fly.io EdgeGlobal< 50ms anywhereStatic assets, promo site

Edge Computing vs. Traditional Architecture

AspectCentralizedEdgePrismatic Approach
LatencyHigher (user -> origin)Lower (user -> nearest edge)Fly.io multi-region for LiveView
Data LocalityAll data at originData replicated to edgesRead replicas near users
ConsistencyStrong (single source)Eventual (replication lag)Strong for writes (primary), eventual for reads
Failure DomainSingle point of failureIsolated edge failuresMulti-region with automatic failover
CostLower (single location)Higher (replication overhead)Optimized (primary + 1-2 replicas)
ComplexitySimplerHigher (distribution)Fly.io manages distribution layer

Technical Deep Dive

Graph Edges in KuzuDB

KuzuDB, the Prismatic Platform's graph database, stores edges as first-class citizens with rich property support. Unlike relational databases where relationships are implicit (join tables), graph databases make relationships explicit and traversable without joins, enabling sub-millisecond traversal of complex relationship networks.

Edge storage in KuzuDB uses a Compressed Sparse Row (CSR) format that enables O(1) neighbor lookup for a given vertex. This is critical for DD graph queries where an analyst might ask "show me all entities connected to this company within 3 hops" -- a query that would require 3 self-joins in a relational database but is a simple BFS in a graph database.

Temporal edges in KuzuDB support valid_from and valid_to properties, enabling point-in-time queries like "what was the ownership structure of this company on 2023-01-15?" This is essential for DD investigations where historical relationships often reveal patterns invisible in current-state snapshots.

Edge Weight Semantics in OSINT

In OSINT knowledge graphs, edge weights carry specific semantic meaning depending on the relationship type:

Weight RangeConfidence LevelSource QualityAnalyst Action
0.9 - 1.0VerifiedOfficial registry, court recordAccept as fact
0.7 - 0.89High confidenceMultiple corroborating sourcesAccept with note
0.5 - 0.69ModerateSingle reliable sourceVerify independently
0.3 - 0.49Low confidenceIndirect inference, media reportFlag for investigation
0.0 - 0.29UnverifiedRumor, single unverified sourceTreat as hypothesis

LiveView WebSocket Edge Optimization

For LiveView applications, edge proximity to users directly determines UX quality. Each user interaction (click, form input, navigation) requires a WebSocket round-trip: browser -> edge server -> process event -> render diff -> send diff -> browser update.

The total perceived latency is: RTT + server_processing + diff_computation + browser_patch. With server processing typically under 10ms for well-optimized LiveViews, the RTT becomes the dominant factor. Deploying at edge locations reduces RTT from ~80-150ms (cross-Atlantic) to ~10-25ms (same-continent), making LiveView interactions feel instantaneous.

Fly.io's Anycast networking automatically routes users to the nearest edge location without DNS-based geolocation, providing optimal routing even for users behind VPNs or CDNs that obscure geographic location.

Usage in Prismatic Platform

Graph Edges in DD Investigations

The DD knowledge graph uses edges to represent every discovered relationship between entities. When an analyst investigates a company, the system automatically discovers and graphs ownership edges (from company registries like ARES), employment edges (from public filings), financial flow edges (from transaction analysis), legal edges (from court proceedings), and association edges (from co-occurrence in media and documents).

The graph traversal engine enables complex queries like "find all paths between Entity A and Entity B through intermediaries" or "identify all entities within 2 hops of a sanctioned person". These queries leverage KuzuDB's native Cypher-like query language with temporal filters to reconstruct relationship networks at any point in time.

Edge Deployment Architecture

The Prismatic Platform deploys to Fly.io edge locations with a primary instance in Frankfurt (EU Central) serving the PostgreSQL database and write operations, and read replicas distributed across additional edge locations. LiveView WebSocket connections are terminated at the nearest edge, with Fly.io's internal networking providing low-latency communication between edge instances and the primary database.

Static assets (JavaScript bundles, CSS, images) are served from Fly.io's global CDN edge network, ensuring sub-50ms asset delivery worldwide. The promo site (Zola-generated static content) is entirely edge-served with no origin dependency.

Code Examples

defmodule PrismaticDd.Graph.Edge do
  @moduledoc """
  Represents relationships between DD entities in the knowledge graph.

  Edges connect entities with typed, weighted, and temporal relationships,
  enabling rich graph traversal for intelligence analysis. Each edge
  carries a relationship type, confidence weight, arbitrary properties,
  and optional temporal validity bounds.

  ## Edge Lifecycle

  Edges are created when relationships are discovered during DD
  investigations (from OSINT sources, public registries, financial
  records, etc.). They are updated when new evidence changes the
  confidence weight or extends the validity period. They are never
  deleted -- instead, expired edges have their `valid_to` set to the
  expiration timestamp, preserving the historical record.

  ## Examples

      iex> edge = PrismaticDd.Graph.Edge.create("company-1", "person-1", :employs, weight: 0.95)
      iex> edge.relationship_type
      :employs

      iex> PrismaticDd.Graph.Edge.active?(edge)
      true
  """

  @type relationship_type ::
    :owns | :manages | :employs | :directs | :founded |
    :related_to | :transacts_with | :litigates_against |
    :subsidiary_of | :partner_of | :audits | :finances

  @type t :: %__MODULE__{
    id: String.t(),
    source_id: String.t(),
    target_id: String.t(),
    relationship_type: relationship_type(),
    weight: float(),
    properties: map(),
    valid_from: DateTime.t() | nil,
    valid_to: DateTime.t() | nil,
    source_evidence: list(String.t()),
    created_at: DateTime.t()
  }

  defstruct [
    :id, :source_id, :target_id, :relationship_type,
    weight: 1.0, properties: %{}, valid_from: nil, valid_to: nil,
    source_evidence: [], created_at: nil
  ]

  @doc """
  Creates a new edge between two entities with the specified
  relationship type and optional properties.

  ## Parameters

    - `source_id` - The originating entity ID
    - `target_id` - The destination entity ID
    - `rel_type` - The relationship type atom
    - `opts` - Optional keyword list:
      - `:weight` - Confidence weight (0.0-1.0, default: 1.0)
      - `:properties` - Additional metadata map
      - `:valid_from` - Start of validity period
      - `:valid_to` - End of validity period
      - `:source_evidence` - List of evidence source identifiers

  ## Examples

      iex> edge = PrismaticDd.Graph.Edge.create("co-1", "per-1", :employs, weight: 0.9, properties: %{role: "CEO"})
      iex> edge.weight
      0.9

      iex> edge = PrismaticDd.Graph.Edge.create("co-1", "co-2", :subsidiary_of)
      iex> edge.relationship_type
      :subsidiary_of
  """
  @spec create(String.t(), String.t(), relationship_type(), keyword()) :: t()
  def create(source_id, target_id, rel_type, opts \\ []) do
    %__MODULE__{
      id: Ecto.UUID.generate(),
      source_id: source_id,
      target_id: target_id,
      relationship_type: rel_type,
      weight: Keyword.get(opts, :weight, 1.0),
      properties: Keyword.get(opts, :properties, %{}),
      valid_from: Keyword.get(opts, :valid_from),
      valid_to: Keyword.get(opts, :valid_to),
      source_evidence: Keyword.get(opts, :source_evidence, []),
      created_at: DateTime.utc_now()
    }
  end

  @doc """
  Checks whether an edge is currently active (not expired).

  An edge with no `valid_to` is always active. An edge with a
  `valid_to` in the past is inactive.

  ## Examples

      iex> edge = %PrismaticDd.Graph.Edge{valid_to: nil}
      iex> PrismaticDd.Graph.Edge.active?(edge)
      true

      iex> edge = %PrismaticDd.Graph.Edge{valid_to: ~U[2020-01-01 00:00:00Z]}
      iex> PrismaticDd.Graph.Edge.active?(edge)
      false
  """
  @spec active?(t()) :: boolean()
  def active?(%__MODULE__{valid_to: nil}), do: true
  def active?(%__MODULE__{valid_to: valid_to}) do
    DateTime.compare(DateTime.utc_now(), valid_to) == :lt
  end

  @doc """
  Checks whether an edge matches a given relationship type or list of types.

  ## Examples

      iex> edge = PrismaticDd.Graph.Edge.create("a", "b", :owns)
      iex> PrismaticDd.Graph.Edge.matches_type?(edge, :owns)
      true

      iex> PrismaticDd.Graph.Edge.matches_type?(edge, [:owns, :manages])
      true

      iex> PrismaticDd.Graph.Edge.matches_type?(edge, :employs)
      false
  """
  @spec matches_type?(t(), relationship_type() | list(relationship_type())) :: boolean()
  def matches_type?(%__MODULE__{relationship_type: type}, types) when is_list(types) do
    type in types
  end
  def matches_type?(%__MODULE__{relationship_type: type}, type), do: true
  def matches_type?(_, _), do: false

  @doc """
  Returns the confidence classification for an edge's weight.

  ## Examples

      iex> edge = PrismaticDd.Graph.Edge.create("a", "b", :owns, weight: 0.95)
      iex> PrismaticDd.Graph.Edge.confidence_level(edge)
      :verified

      iex> edge = PrismaticDd.Graph.Edge.create("a", "b", :owns, weight: 0.4)
      iex> PrismaticDd.Graph.Edge.confidence_level(edge)
      :low
  """
  @spec confidence_level(t()) :: :verified | :high | :moderate | :low | :unverified
  def confidence_level(%__MODULE__{weight: w}) when w >= 0.9, do: :verified
  def confidence_level(%__MODULE__{weight: w}) when w >= 0.7, do: :high
  def confidence_level(%__MODULE__{weight: w}) when w >= 0.5, do: :moderate
  def confidence_level(%__MODULE__{weight: w}) when w >= 0.3, do: :low
  def confidence_level(_), do: :unverified
end
defmodule PrismaticDd.Graph.Traversal do
  @moduledoc """
  Graph traversal algorithms for DD entity relationship analysis.

  Provides BFS, DFS, shortest path, and all-paths algorithms
  operating on the DD knowledge graph. Traversals respect temporal
  bounds and minimum confidence thresholds, ensuring analysts only
  see relationships that are current and sufficiently verified.

  ## Examples

      iex> {:ok, paths} = PrismaticDd.Graph.Traversal.find_paths("entity-a", "entity-b", max_depth: 3)
      iex> is_list(paths)
      true
  """

  alias PrismaticDd.Graph.Edge

  @type traversal_opts :: [
    max_depth: pos_integer(),
    min_confidence: float(),
    relationship_types: list(atom()),
    temporal_filter: DateTime.t() | nil
  ]
  @type path :: list(Edge.t())

  @doc """
  Finds all paths between two entities within the given constraints.

  ## Parameters

    - `source_id` - Starting entity ID
    - `target_id` - Destination entity ID
    - `opts` - Traversal options:
      - `:max_depth` - Maximum path length (default: 4)
      - `:min_confidence` - Minimum edge weight (default: 0.3)
      - `:relationship_types` - Filter by relationship types (default: all)
      - `:temporal_filter` - Point-in-time filter (default: current)

  ## Examples

      iex> {:ok, paths} = PrismaticDd.Graph.Traversal.find_paths("co-1", "per-1", max_depth: 2)
      iex> Enum.all?(paths, &is_list/1)
      true
  """
  @spec find_paths(String.t(), String.t(), traversal_opts()) :: {:ok, list(path())} | {:error, term()}
  def find_paths(source_id, target_id, opts \\ []) do
    max_depth = Keyword.get(opts, :max_depth, 4)
    min_confidence = Keyword.get(opts, :min_confidence, 0.3)

    paths = do_find_paths(source_id, target_id, max_depth, min_confidence, [], MapSet.new())
    {:ok, paths}
  end

  @doc """
  Performs breadth-first search from a source entity, returning all
  entities reachable within `max_depth` hops.

  ## Examples

      iex> {:ok, reachable} = PrismaticDd.Graph.Traversal.bfs("entity-1", max_depth: 2)
      iex> is_list(reachable)
      true
  """
  @spec bfs(String.t(), traversal_opts()) :: {:ok, list(map())} | {:error, term()}
  def bfs(source_id, opts \\ []) do
    max_depth = Keyword.get(opts, :max_depth, 3)
    min_confidence = Keyword.get(opts, :min_confidence, 0.3)

    result = do_bfs([{source_id, 0}], MapSet.new([source_id]), [], max_depth, min_confidence)
    {:ok, result}
  end

  @spec do_find_paths(String.t(), String.t(), non_neg_integer(), float(), path(), MapSet.t()) :: list(path())
  defp do_find_paths(_source, _target, 0, _min_conf, _current_path, _visited), do: []

  defp do_find_paths(source, target, max_depth, min_conf, current_path, visited) do
    neighbors = get_neighbors(source, min_conf)

    Enum.flat_map(neighbors, fn edge ->
      next_id = edge.target_id
      new_path = current_path ++ [edge]

      cond do
        next_id == target ->
          [new_path]

        MapSet.member?(visited, next_id) ->
          []

        true ->
          do_find_paths(next_id, target, max_depth - 1, min_conf, new_path, MapSet.put(visited, next_id))
      end
    end)
  end

  @spec do_bfs(list({String.t(), non_neg_integer()}), MapSet.t(), list(map()), non_neg_integer(), float()) :: list(map())
  defp do_bfs([], _visited, results, _max_depth, _min_conf), do: results

  defp do_bfs([{_id, depth} | rest], visited, results, max_depth, min_conf) when depth > max_depth do
    do_bfs(rest, visited, results, max_depth, min_conf)
  end

  defp do_bfs([{id, depth} | rest], visited, results, max_depth, min_conf) do
    neighbors = get_neighbors(id, min_conf)

    new_entries =
      neighbors
      |> Enum.reject(fn edge -> MapSet.member?(visited, edge.target_id) end)
      |> Enum.map(fn edge -> {edge.target_id, depth + 1} end)

    new_visited = Enum.reduce(new_entries, visited, fn {nid, _}, vs -> MapSet.put(vs, nid) end)
    new_results = results ++ Enum.map(new_entries, fn {nid, d} -> %{entity_id: nid, depth: d} end)

    do_bfs(rest ++ new_entries, new_visited, new_results, max_depth, min_conf)
  end

  @spec get_neighbors(String.t(), float()) :: list(Edge.t())
  defp get_neighbors(_entity_id, _min_confidence) do
    # In production, queries KuzuDB for outgoing edges
    []
  end
end

Common Pitfalls

PitfallProblemSolution
Generic edge labelsUsing "related_to" for all relationships loses analytical valueDefine specific relationship types (:owns, :employs, :manages) with clear semantics
Missing temporal boundsTreating historical relationships as current produces false intelligenceAlways set valid_from/valid_to on edges; query with temporal filters
Unweighted edgesTreating all relationships as equally confident leads to false conclusionsAssign confidence weights based on source quality; filter by minimum confidence
Unbounded traversalsGraph traversal without depth limits can explore the entire graphAlways set max_depth; start with 2-3 hops and increase if needed
Ignoring edge directionTreating directed edges as undirected corrupts relationship semanticsRespect edge direction in queries; use explicit bidirectional edges when needed
Edge deployment without monitoringDeploying to edge locations without latency monitoring misses performance regressionsMonitor p50/p95/p99 latency per edge region; alert on degradation
Single-region dependencyAll traffic to one region creates a single point of failureDeploy to at least 2 regions with automatic failover
Large WebSocket payloadsSending large diffs over LiveView WebSocket negates edge latency benefitsOptimize LiveView assigns to minimize diff size; use temporary assigns
Circular ownership graphsCircular relationships without cycle detection cause infinite traversalImplement cycle detection; maintain visited set during traversal
Stale edge cacheCaching graph query results without invalidation serves outdated relationshipsUse TTL-based cache invalidation; invalidate on edge create/update

Best Practices

  1. Type all edges with meaningful relationship names -- generic edges like "related_to" carry less analytical value than specific types like "employs", "owns", or "litigates_against".
  2. Include temporal validity on edges -- relationships change over time; temporal edges enable historical analysis and point-in-time reconstruction essential for DD investigations.
  3. Weight edges by source confidence -- in OSINT contexts, relationship confidence varies by source quality; edge weights from the Nabla epistemic engine capture this uncertainty quantitatively.
  4. Index frequently traversed edge types -- graph query performance depends on efficient edge traversal; create indexes on common relationship types in KuzuDB.
  5. Deploy at edges closest to users -- Fly.io edge regions reduce p95 latency for LiveView WebSocket connections from 80-150ms to 10-25ms.
  6. Implement bounded traversals -- always set max_depth on graph traversals; unbounded traversals can explore millions of edges and time out.
  7. Preserve edge history -- never delete edges; set valid_to to expire them, preserving the complete historical record for audit and investigation replay.
  8. Monitor edge region health -- track latency, error rates, and connection counts per edge deployment region; implement automatic failover.
  9. Use CSR-optimized storage -- KuzuDB's Compressed Sparse Row format provides O(1) neighbor lookup; leverage this for performance-critical traversals.
  10. Combine graph and relational queries -- use graph edges for relationship traversal and relational queries for attribute filtering; not every query benefits from graph representation.
  • Digraph -- Erlang's directed graph implementation with in-memory edge storage
  • Graph Database -- persistent storage optimized for edge-centric queries and traversals
  • KuzuDB -- the Prismatic Platform's graph database storing DD entity relationships
  • Graph Theory -- mathematical foundations of edge properties, traversal algorithms, and graph metrics
  • Vertex -- the nodes connected by edges in a graph structure
  • Fly.io -- edge deployment platform providing multi-region infrastructure
  • Distributed Erlang -- BEAM clustering enabling communication between edge-deployed nodes
  • Latency -- the primary metric optimized by edge deployment strategies
  • LiveView -- Phoenix LiveView requiring low-latency WebSocket connections to edge servers
  • CDN -- content delivery networks serving static assets from edge locations
  • WebSocket -- persistent connections between browsers and edge servers for real-time updates
  • Relationship -- the semantic meaning carried by edges in knowledge graphs

See Also

  • Technologies -- graph and edge computing technologies in the platform stack
  • Architecture -- platform graph database and multi-region deployment architecture
  • Capabilities -- intelligence analysis and edge deployment capabilities

Connect & Contribute

Created by Tomas Korcak (korczis) | Open Source under GHL