View Source mix bond.audit (Bond v1.19.0)

Which public functions carry a Bond contract, and which carry none.

The one thing Bond.Coverage structurally cannot tell you. Its table reports assertions that ran, so it is a report about contracts that exist: a function nobody contracted appears nowhere in it, and neither does the module it lives in.

That absence is the failure mode the contract-writing rules single out as the dangerous one, precisely because nothing shows it:

The failure mode is one-sided: a codebase with too few contracts looks exactly like one that never needed them, so careful screening under-contracts by default and nobody notices.

This turns it into a number.

mix bond.audit                 # the summary
mix bond.audit --verbose       # and every uncontracted function
mix bond.audit --only Tidal    # restrict to matching module names
mix bond.audit --min 40        # exit non-zero below 40%, for a ratchet

It reads the compiler, not the source

Every module that use Bond exports a __bond_contracted__/0 reflection listing the functions it contracted, built by the compiler from what it actually annotated. Nothing here parses Elixir.

That matters more than it sounds. A source-parsing audit cannot see a function generated by a macro, a module that acquired use Bond through some other __using__, or a defdelegate — and it has to guess at elixirc_paths and umbrella layouts. The compiler already knows.

A module that uses Bond and contracts nothing exports %{}, which is a different answer from not using Bond at all, and the more interesting one.

What it counts, and what it deliberately does not

The denominator is public, non-callback functions in modules that use Bond. Three exclusions, each because including it would measure something else:

  • A module that does not use Bond cannot carry a contract. Adding use Bond to a module is a separate decision from contracting a function in it.
  • defp is reported but not counted. Private functions can and should carry contracts; they are not part of a module's promise to its callers, so a density over them answers a different question.
  • @impl callbacks are counted apart. What a callback promises is the behaviour's to state — see Bond.Behaviour — so their absence from an implementation's own contracts is not the same finding. They are detected from the module's @behaviour attributes rather than from @impl, which is both more accurate and available at runtime.

A function that appears in the reflection carrying only a module @invariant is counted apart as well. Bond checks that invariant around it, so it is real coverage; but an invariant is a law about the type rather than a promise about any one function, and merging the two would flatter the number.

It is a prompt, not a verdict

A bare function is a question — "what does this promise?" — and some honest answers are "nothing beyond its @spec". The reasons to decline a contract are short: it is unsound, it is unreachable, or the specification does not warrant it. "There are a lot of them" is not among them, so a large count here is a backlog rather than a defect.