ADR-014: Output Format Strategy¶
Date: 2026-03-18
Status: Accepted — implemented. Amendment (2026-07-14): the "no
information loss" / "no format-specific data loss" claims below are too
strong and were superseded by ADR-036 (Report view-model and canonical
report severity), which documents that formats deliberately diverge on
classification axis (SARIF uses a finer per-kind severity, ABICC-compat
HTML uses ABICC's own HIGH/MEDIUM/LOW) and, as verified directly against the
formatter code for this amendment, on field coverage too: JSON carries the
full DiffResult (including detectors[] coverage-gap info and the
suppressed_changes[] list — see abicheck/reporter.py); SARIF and HTML
carry per-change old_value/new_value/affected_symbols as structured
fields (abicheck/sarif.py, abicheck/html_report.py) but no detector
list; JUnit XML (abicheck/junit_report.py) embeds old/new values as
free-text inside the <failure> body rather than structured attributes, and
has no equivalent of the suppressed-changes section at all (verified: no
suppress* reference anywhere in junit_report.py). What is still
guaranteed across all native channels (JSON, Markdown/text, JUnit) is the
breaking-boundary and override-propagation invariant from ADR-036 — read
that ADR for the authoritative cross-channel contract instead of the
paragraph below.
Decision maker: Nikolay Petrov
Context¶
abicheck results must be consumable by:
- Humans reading terminal output or CI logs
- CI systems parsing machine-readable output for gate decisions
- GitHub Code Scanning ingesting SARIF for PR annotations
- Web browsers for standalone report viewing
No single format serves all consumers. The output format strategy defines which formats are supported, what contract each format provides, and how format selection works.
Decision¶
Five output formats¶
| Format | Primary consumer | CLI flag | Default? |
|---|---|---|---|
| Markdown | Humans (terminal, CI logs) | --format markdown |
Yes |
| JSON | Automation, AI agents, scripts | --format json |
No |
| SARIF 2.1.0 | GitHub Code Scanning | --format sarif |
No |
| HTML | Standalone report viewing | --format html |
No |
| JUnit XML | GitLab CI, Jenkins, Azure DevOps | --format junit |
No |
Markdown (default)¶
- Rendered in monospace terminals and CI log viewers
- Sections: verdict banner, summary table, changes grouped by severity (breaking → source breaks → risk → compatible)
- Emoji verdict indicators: ❌ (BREAKING), ⚠️ (API_BREAK/RISK), ✅ (COMPATIBLE/NO_CHANGE)
- Demangled symbol names for readability
Markdown is the default because it works everywhere — terminals, GitHub PR comments, CI log viewers, README files — without requiring special rendering.
JSON¶
- Machine-readable structured output
- Top-level fields:
library,verdict,summary,changes[],suppressed_changes[],detectors[] - Summary includes:
breaking_count,source_breaks,risk_count,compatible_additions,total_changes,binary_compatibility_pct,affected_pct - Each change includes:
kind,symbol,description,old_value,new_value,source_location,affected_symbols - Library metadata: path, SHA-256 hash, file size
- Detector results: name, changes count, enabled status, coverage gaps
JSON output uses the same DiffResult data as the other formats and is the
highest-fidelity format (see the amendment note above for the per-format
field-coverage differences verified against the formatter code).
SARIF 2.1.0¶
- Targets GitHub Code Scanning (upload via
github/codeql-action/upload-sarif) - SARIF specification: OASIS SARIF v2.1.0
- Mapping:
- Each
ChangeKind→ SARIF rule (rule ID =ChangeKind.value) BREAKING→ SARIF levelerrorAPI_BREAK→ SARIF levelwarningCOMPATIBLE_WITH_RISK→ SARIF levelwarningCOMPATIBLE→ SARIF levelnote- Tool version from
importlib.metadata.version("abicheck") - Results include source locations (when available from headers)
HTML¶
- Self-contained single file — no external CSS, JavaScript, or images
- ABICC-inspired layout for familiarity (but not format-compatible)
- Verdict banner with color coding:
- BREAKING: red (
#b71c1c/#ffcdd2) - COMPATIBLE_WITH_RISK: orange (
#e65100/#fff3e0) - COMPATIBLE: green (
#1b5e20/#c8e6c9) - Binary Compatibility % metric (based on old exported symbol count)
- Sectioned change tables: Removed | Changed | Added
- Demangled names displayed, mangled names as tooltips
- Suppressed changes section (if any)
Self-contained HTML was chosen over an external-stylesheet approach to ensure reports can be emailed, archived, or opened offline without broken rendering.
JUnit XML¶
- Targets CI systems with JUnit test result dashboards (GitLab CI, Jenkins, Azure DevOps, CircleCI)
- Mapping:
- Each library →
<testsuite> - Each exported symbol/type →
<testcase> classnamegroups:functions,variables,types,enums,metadataBREAKING/API_BREAK→<failure>elementCOMPATIBLE_WITH_RISK→<failure>only when per-kind severity iserrorCOMPATIBLE→ passing test case (no<failure>child)- When old snapshot is available, unchanged symbols appear as passing tests for a meaningful pass-rate
- Uses
xml.etree.ElementTree(stdlib) — no external dependency
JUnit was chosen over a proprietary CI-specific format because all major CI platforms support JUnit natively, making it the best single format for broad CI integration.
Format selection¶
abicheck compare old.so new.so # Markdown (default)
abicheck compare old.so new.so --format json # JSON
abicheck compare old.so new.so --format sarif # SARIF
abicheck compare old.so new.so --format html # HTML
abicheck compare old.so new.so --format html -o report.html # HTML written to file
abicheck compare old.so new.so --format junit -o results.xml # JUnit XML
The format must be explicitly selected via --format. The -o / --output
flag only controls where output is written — it does not infer format from
the file extension. If --format is omitted, the default is markdown
regardless of the output filename.
Information preservation¶
All five formats are generated from the same DiffResult object, and
verdict and exit code computation is independent of output format. See the
amendment note at the top of this ADR and ADR-036 for the precise,
verified cross-channel contract — formats are not byte-for-byte
interchangeable projections of identical fields (SARIF/JUnit/HTML each omit
or reshape some DiffResult fields relative to JSON).
Consequences¶
Positive¶
- Every consumer has a first-class output format
- GitHub Code Scanning integration via standard SARIF — no custom tooling
- Self-contained HTML enables offline report archival
- Markdown default works everywhere with zero configuration
- JSON/SARIF preserve enough structured detail for automation; see the amendment note above and ADR-036 for what each format actually carries (it is not uniform across formats)
Negative¶
- Five formatters to maintain (reporter.py, sarif.py, html_report.py, junit_report.py)
- SARIF severity mapping is a compatibility contract with GitHub
- Self-contained HTML generates larger files than external-CSS approaches
- JSON schema evolves with the project (see ADR-015 for schema versioning)
References¶
abicheck/reporter.py— Markdown and JSON formattingabicheck/sarif.py— SARIF 2.1.0 outputabicheck/html_report.py— HTML report generationabicheck/junit_report.py— JUnit XML outputabicheck/cli.py—--formatflag and output file handling- ADR-036 — Report view-model and canonical report severity; the authoritative cross-channel contract superseding the "no information loss" claims in this ADR