Mix.Statifier.GateGuard (Statifier v2.0.0)

Copy Markdown View Source

Finds changes to the quality gate's own configuration that no one has justified in writing.

ADR-0011 says the gate's config is not agent-editable: a red gate is never turned green by lowering a threshold, skipping a check, tagging a test @tag :skip, or shrinking the regression ratchet. This module is the mechanical half of that policy. It reads a diff of the current branch against its base and reports a finding for every guarded change the diff does not also record in docs/quality-gate-changes.md.

The check deliberately cannot tell a weakening from a strengthening, and does not try. Any guarded change needs a ledger entry; the entry is where a human says which it was.

analyze/1 is pure - it takes a diff and two registry snapshots and returns findings. collect/1 is the part that talks to git, and takes an opts[:runner] so tests never need a fixture repository.

Summary

Functions

Turns a diff and two registry snapshots into unjustified-change findings.

Reads the diff and registry snapshots the guard needs out of git.

The gate-config files any edit to which needs a ledger entry.

Path of the justification ledger, relative to the project root.

Types

finding()

@type finding() :: %{
  file: String.t(),
  line: pos_integer() | nil,
  severity: String.t(),
  check: String.t(),
  message: String.t()
}

source()

@type source() :: %{
  diff: String.t(),
  base_registry: String.t() | nil,
  head_registry: String.t() | nil
}

Functions

analyze(source)

@spec analyze(source :: source()) :: [finding()]

Turns a diff and two registry snapshots into unjustified-change findings.

Findings cleared by a ledger entry added in the same diff are dropped, so a ledger entry written on some other branch clears nothing here.

collect(opts)

@spec collect(opts :: keyword()) ::
  {:ok, source()} | {:error, String.t()} | :no_base_ref

Reads the diff and registry snapshots the guard needs out of git.

Base ref resolution is opts[:base], then origin/main, then main. When none of them resolves there is nothing to diff against, so this returns :no_base_ref rather than guessing a base; the task turns that into a skipped stage.

opts[:runner] replaces the git shell-out with a function of an argument list returning {output, status}, mirroring Mix.Tasks.Test.Regression.

guarded_paths()

@spec guarded_paths() :: [String.t()]

The gate-config files any edit to which needs a ledger entry.

ledger_path()

@spec ledger_path() :: String.t()

Path of the justification ledger, relative to the project root.