Firebreak.Conformance (Firebreak v0.1.0)

Copy Markdown View Source

Supervision-topology conformance: diff the intended tree against the one the code actually produces, and report the drift.

Firebreak already projects each supervisor into a model bundle (Firebreak.Model). The same projection doubles as a committable spec of the intended topology: a team snapshots it on a known-good commit, commits the file, and a later run compares the current topology to it. What changed — a strategy flipped to :one_for_all, a child dropped out of a supervisor, the restart intensity loosened — surfaces as topology_drift findings that gate in CI just like any other finding.

This is the structural sibling of the finding baseline (Firebreak.Baseline): the baseline pins the findings you've accepted; conformance pins the shape of the tree you designed. The diff is the same idea Uppsala's process-structure work framed as "compare the specification to the abstraction of the implementation" — here the specification is just a prior projection of the implementation.

Spec format

The committed file is a plain Elixir term (read with Code.eval_string, like .firebreak.exs) — a list of per-supervisor maps:

[
  %{
    supervisor: MyApp.Sup,
    strategy: :one_for_one,
    max_restarts: 3,
    max_seconds: 5,
    children: [
      %{module: MyApp.Cache, restart: :permanent, type: :worker},
      %{module: MyApp.Pool, restart: :permanent, type: :supervisor}
    ]
  }
]

Generate it with mix firebreak --write-expected FILE; check against it with mix firebreak --expect FILE.

Summary

Functions

Append topology_drift findings (current topology vs path's expected topology) to the analysis. A missing/unreadable spec leaves the analysis unchanged and returns {analysis, :no_spec} so the caller can warn.

Topology-drift findings comparing the current analysis to an expected projection.

Read an expected-topology file written by write/2. Returns the projection list, or nil if the file is missing or unparseable (the caller notes that rather than failing).

The committable projection of the current topology: one reduced bundle per supervisor (strategy, intensity, ordered children), sorted by supervisor name for a stable file.

Write the current topology projection to path as a pretty Elixir term.

Types

sup_spec()

@type sup_spec() :: %{
  supervisor: module(),
  strategy: atom() | nil,
  max_restarts: non_neg_integer() | nil,
  max_seconds: non_neg_integer() | nil,
  children: [%{module: module(), restart: atom(), type: atom()}]
}

Functions

check(a, path)

@spec check(Firebreak.Analysis.t(), Path.t()) ::
  {Firebreak.Analysis.t(), :ok | :no_spec}

Append topology_drift findings (current topology vs path's expected topology) to the analysis. A missing/unreadable spec leaves the analysis unchanged and returns {analysis, :no_spec} so the caller can warn.

diff(current, expected)

Topology-drift findings comparing the current analysis to an expected projection.

load(path)

@spec load(Path.t()) :: [sup_spec()] | nil

Read an expected-topology file written by write/2. Returns the projection list, or nil if the file is missing or unparseable (the caller notes that rather than failing).

projection(a)

@spec projection(Firebreak.Analysis.t()) :: [sup_spec()]

The committable projection of the current topology: one reduced bundle per supervisor (strategy, intensity, ordered children), sorted by supervisor name for a stable file.

write(path, a)

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

Write the current topology projection to path as a pretty Elixir term.