Firebreak reads the supervision structure of an Elixir/OTP application and reports where the declared failure topology (the supervision tree) diverges from the actual one (the runtime coupling between processes).
The supervision tree tells you what each supervisor contains. It does not tell you who, from outside a subtree, calls into it — and those are exactly the dependencies that turn a "contained" restart into a cascade. Firebreak recovers both structures and reports the gap.
How the two structures are recovered
- Supervision tree — preferred exact, via the same
Mod.init/1call OTP makes (seeFirebreak.Runtime); static AST parsing (Firebreak.Source) is the fallback for code that can't be loaded. - Coupling graph — always static: who calls/casts/looks-up whom is read
from the AST, since
init/1doesn't reveal it.
Usage
analysis = Firebreak.analyze("path/to/app")
IO.puts(Firebreak.Report.text(analysis))Or via the Mix task in the target project:
mix firebreak
mix firebreak --format json
Summary
Functions
Analyse a project rooted at path.
Analyse an already-parsed list of modules (the testable core).
Convenience: analyse a single source string (handy in tests/iex).
Functions
@spec analyze( Path.t(), keyword() ) :: Firebreak.Analysis.t()
Analyse a project rooted at path.
Parses the source statically, then upgrades each loadable supervisor's tree to
exact runtime data via Firebreak.Runtime. Pass runtime: false to stay
purely static. Pass observe: "node@host" (and optionally cookie:) to fold
in the live shape of a running node via Firebreak.Observe.
@spec analyze_modules([Firebreak.ModuleInfo.t()]) :: Firebreak.Analysis.t()
Analyse an already-parsed list of modules (the testable core).
Operates on whatever modules it is handed — static or runtime-enriched — and does not itself touch the target runtime, so it stays pure and deterministic.
@spec analyze_string(String.t(), String.t() | nil) :: Firebreak.Analysis.t()
Convenience: analyse a single source string (handy in tests/iex).