Skip to content

Report the Surface, Not Only the Breaks

Growth is a change to the contract

Every added public symbol, type or field is compatible today and a promise tomorrow: the next release has to keep it, and every consumer that starts using it is a consumer you now cannot break. A report that lists only breaks therefore answers half the question. "0 breaks" means nothing was taken away; it does not mean nothing was added, and for a frozen API — a plugin ABI, a stable SDK line, a library whose consumers are rebuilt on a schedule you do not control — an unplanned addition is the break.

abicheck reports additions as COMPATIBLE, which Verdicts defines; this page is about making that category visible instead of letting it drown under a green check.

Four signals

Per-symbol additions

Every added function, variable or type is its own finding in the COMPATIBLE (addition) category, with the same identity and location as a break: case03 (a new exported function), case61 (a new global), case62 (a field added to an opaque type). Read them in the report the way you read removals — as a list to review, not a count to admire.

Aggregate roll-ups

Three surface-metric findings describe the shape of the change rather than its members: public_surface_grew, public_surface_shrank, and undocumented_export_ratio_increased (the fraction of exported symbols with no public-header declaration went up — the export table is growing faster than the documented contract). They are informational and computed unconditionally (ADR-027 Phase 5's later default flip; a later CLI-surface cleanup removed the selector flag this section used to name entirely — these findings are ordinary changes[] entries, no flag needed):

abicheck compare old.json new.so -H include/ -o json=-

The release recommendation

Under the release profile the report carries release_recommendation — the SONAME or SemVer action the change set implies, which turns "additions present" into "this is a minor release, not a patch":

abicheck compare last-release.json new.so -H include/ --depth source -o markdown=-

The field's shape is owned by Output Formats § Release recommendation.

Growth you did not intend

The one-build audit (a scan with no --against) finds surface that grew without anyone deciding it should: an accidental export (case143) or an export with no version node under a versioning scheme (case145). Both are additions to your contract that no review approved.

Report or gate?

Additions gate CI only when you say so. A .abicheck.yml severity: block with addition: error (or an Action severity-preset that includes it) turns them into exit 1 — there is no dedicated Action input for additions. That is right for a frozen API, where any growth needs a decision, and noise for a growing SDK, where it would fail every feature PR; for the latter, report and review instead. The Action recipe is in GitHub Action Recipes § Detect unintentional API expansion; the severity categories and how each maps to an exit code are owned by Severity.

Make it visible on the PR

With annotate: true and annotate-additions: true (the second has no effect without the first) the Action posts each addition as a ::notice annotation on the diff, and the sticky PR comment recipe keeps one always-current summary at the top of the conversation. How findings map to annotation levels is owned by Annotations.

Trend it

The roll-ups and the addition count are plain JSON fields; keeping them per release in whatever dashboard you already have shows the contract's growth rate, which is the number a frozen-API team wants to see stay at zero and a growing-SDK team wants to see match its roadmap. Nothing here claims a tool for that beyond the fields themselves.


Ladder:Where in the Pipeline · Step 7 · In Practice · Rollout and Governance