Finds stateful processes that no supervisor starts.
A GenServer / :gen_statem / GenStage / Agent is meant to be a
supervised process. If its module appears in no supervisor's subtree — not a
static child, not a DynamicSupervisor child we could recover, not a Broadway
producer — then either it is started unsupervised (no restart on crash) or it
is never started at all. Both are bugs the supervision tree alone won't show.
The signal is sharpest when something else depends on the orphan: a resolved
coupling edge into it means a caller will hit :noproc the moment it reaches
for a process nothing keeps alive. That case is reported at :medium; an
orphan with no inbound callers is reported at :info (it may be dead code, or
started by a path static analysis can't see). Always best-effort: a child spec
assembled at runtime can hide a supervision we genuinely cannot resolve.
Two pieces of evidence (from Firebreak.ModuleInfo) refine that verdict
instead of leaving every unsupervised process under the same hedge:
- Supervised via a builder — if something builds a
Mod.child_spec(...)for the module (often inside a__using__macro or a per-entity wrapper), it is supervised, just through an indirection we can't attribute to a named supervisor. Reported at:infoas context, relabelled so it doesn't read as a defect. - Hand-started — if the module is started by a direct
Mod.start_link/1outside any supervisor, it is genuinely unsupervised, and we can name the exact call site. Reported at:medium(something depends on it) or:low, with the file:line of the start rather than "we couldn't see it".
Summary
Functions
@spec analyze([Firebreak.ModuleInfo.t()], Firebreak.Forest.t(), [Firebreak.Edge.t()]) :: [ Firebreak.Finding.t() ]