A single, transparent supervision-risk number per project (and per supervisor), derived from the model IR — for dashboards and for tracking whether a tree is getting riskier over time.
It is a weighted count of structural hazards, not an absolute grade. Three components, each read straight off the IR:
sync_crossings— synchronous cross-tree crossings (a caller in one subtree blocking on a process in another). The highest-signal hazard.blast_radius— children that co-restart together under:one_for_all/:rest_for_one(a wide shared-fate set).intensity_pressure— supervisors running many children (≥5) on a tight restart budget (≤3 restarts), where one crash-loop takes the whole subtree.
The weights are relative, not calibrated absolutes — the value is the trend and the per-supervisor ranking, which point at where to look. Every score ships with its component breakdown, so it's never a black box. A clean app scores 0.
Summary
Types
@type t() :: %{ score: non_neg_integer(), components: %{ sync_crossings: non_neg_integer(), blast_radius: non_neg_integer(), intensity_pressure: non_neg_integer() }, weights: map(), per_supervisor: [map()] }
Functions
@spec json(Firebreak.Analysis.t()) :: String.t()
The risk score as a JSON string (mix firebreak --format score).
@spec score(Firebreak.Analysis.t()) :: t()
The risk score for an analysis: total, component breakdown, and per-supervisor ranking.