Join the static model IR against a live node's observed reality.
The model IR (Firebreak.Model) is a declared projection — it says
"Worker calls synchronously into Cache, and that crosses a supervision
boundary." --observe reads what is actually running. This backend pairs the
two: for every synchronous cross-tree crossing in the IR, it annotates the
target with its live state — is it alive, how deep is its mailbox, how many
instances are running — answering the question neither view answers alone:
of my static cross-tree crossings, which targets are *hot right now*?This is a projection, not a new judgement. The judgements about runtime
reality already live in findings: runtime_fanout (a supervisor running more
children than static modelled) and runtime_mailbox_backlog (a deep mailbox on
a synchronously-called process). The overlay is the structured ground-truth
layer those findings are derived from, exposed in IR shape so the same
consumers that read --format model can read the live annotation too.
Everything here carries :exact confidence — it is observed, not inferred.
build/1 returns :no_runtime for an analysis produced without --observe
(there is nothing to overlay), so callers can present a clear "pass --observe"
notice rather than an empty artifact.
analysis = Firebreak.analyze("my_app", observe: "my_app@host")
Firebreak.RuntimeOverlay.build(analysis)
#=> %{node: :my_app@host, crossings: [%{from: Worker, to: Cache,
#=> target_alive: true, target_mailbox: 1400, target_instances: 1}, ...], ...}
Summary
Functions
Build the runtime overlay for analysis, or :no_runtime if it was produced
without --observe (no live reading to join against).
The overlay as a JSON string (key order preserved). For an analysis with no
live reading, emits a small object explaining that --observe is required,
rather than failing — so --format overlay is always well-formed.
The overlay schema version (bumped on a breaking shape change).
Types
@type crossing_overlay() :: %{ from: module(), to: module(), in_init: boolean(), target_alive: boolean(), target_mailbox: non_neg_integer() | nil, target_instances: pos_integer() | nil }
@type t() :: %{ schema_version: pos_integer(), node: node(), live_processes: non_neg_integer(), supervisors: [map()], crossings: [crossing_overlay()], live_names: %{optional(atom()) => module()} }
Functions
@spec build(Firebreak.Analysis.t()) :: t() | :no_runtime
Build the runtime overlay for analysis, or :no_runtime if it was produced
without --observe (no live reading to join against).
@spec json(Firebreak.Analysis.t()) :: String.t()
The overlay as a JSON string (key order preserved). For an analysis with no
live reading, emits a small object explaining that --observe is required,
rather than failing — so --format overlay is always well-formed.
@spec schema_version() :: pos_integer()
The overlay schema version (bumped on a breaking shape change).