CI plumbing: a committed allowlist (.firebreak.exs) and a finding baseline so
a pipeline can fail only on new coupling, not on the backlog it inherited.
Two complementary mechanisms:
- Suppression —
.firebreak.exsin the project root lists findings the team has reviewed and accepted. They are dropped from the report entirely. - Baseline diff — a snapshot of the findings present at some known-good commit. A later run reports (and gates on) only the findings whose signature is absent from that snapshot — the regressions.
Both match findings by Firebreak.Finding.signature/1, which is stable across
line moves and message-wording changes, so the allowlist/baseline doesn't churn
every time unrelated code shifts.
.firebreak.exs format
The file evaluates to a map (or a bare list of matchers):
%{
suppress: [
# match every finding of a check on a module
%{check: :cross_tree_coupling, module: MyApp.Cache.Supervisor},
# match every finding on a module, any check
%{module: MyApp.LegacyServer},
# match every finding of a check, any module
%{check: :default_restart_intensity},
# or pin an exact finding by its signature string
"boot_order_dependency:MyApp.Early/MyApp.Late"
]
}
Summary
Functions
Default config filename, looked up relative to the analysed project root.
Keep only findings whose signature is absent from the baseline (the new ones).
Read a baseline file written by write/2 into a set of signatures. A missing
file yields an empty set, so the first run (before any baseline exists) reports
everything as new.
Load suppression matchers from path. A missing file is not an error — it
yields an empty matcher list (suppression simply does nothing).
Drop every finding matched by a suppression matcher.
Write the current findings' signatures to path as a sorted .exs list.
Types
Functions
Default config filename, looked up relative to the analysed project root.
@spec diff(Firebreak.Analysis.t(), MapSet.t()) :: Firebreak.Analysis.t()
Keep only findings whose signature is absent from the baseline (the new ones).
Read a baseline file written by write/2 into a set of signatures. A missing
file yields an empty set, so the first run (before any baseline exists) reports
everything as new.
Load suppression matchers from path. A missing file is not an error — it
yields an empty matcher list (suppression simply does nothing).
@spec suppress(Firebreak.Analysis.t(), [matcher()]) :: Firebreak.Analysis.t()
Drop every finding matched by a suppression matcher.
@spec write(Path.t(), Firebreak.Analysis.t()) :: :ok | {:error, term()}
Write the current findings' signatures to path as a sorted .exs list.