Firebreak.Baseline (Firebreak v0.1.0)

Copy Markdown View Source

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.exs in 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

matcher()

@type matcher() ::
  %{optional(:check) => atom(), optional(:module) => module()} | String.t()

Functions

default_config_name()

Default config filename, looked up relative to the analysed project root.

diff(a, baseline)

Keep only findings whose signature is absent from the baseline (the new ones).

load(path)

@spec load(Path.t()) :: MapSet.t()

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_config(path)

@spec load_config(Path.t()) :: [matcher()]

Load suppression matchers from path. A missing file is not an error — it yields an empty matcher list (suppression simply does nothing).

suppress(a, matchers)

@spec suppress(Firebreak.Analysis.t(), [matcher()]) :: Firebreak.Analysis.t()

Drop every finding matched by a suppression matcher.

write(path, analysis)

@spec write(Path.t(), Firebreak.Analysis.t()) :: :ok | {:error, term()}

Write the current findings' signatures to path as a sorted .exs list.