Skip to content

ADR Gap Analysis — Decisions Needing Formal ADRs

Date: 2026-03-18 Author: Claude Code (automated analysis) Purpose: Identify implemented project decisions/assumptions that lack a formal ADR


Methodology

This analysis cross-references: 1. Existing ADRs (001–008, later extended to 001–019) and their coverage 2. Implemented code in abicheck/ (114 ChangeKinds, policy system, output formats, CLI, etc.) 3. pyproject.toml, CI workflows, GitHub Action, and documentation 4. GOALS.md and CHANGELOG.md for stated-but-undocumented decisions

Each candidate is rated by urgency: - HIGH — Decision is complex, non-obvious, and actively affects contributors/users - MEDIUM — Decision is important but stable; documenting prevents future confusion - LOW — Decision is straightforward or unlikely to be revisited


Candidate ADR 1: Verdict System and Exit Code Contract

Urgency: HIGH

What exists: The 5-tier verdict system (NO_CHANGE, COMPATIBLE, COMPATIBLE_WITH_RISK, API_BREAK, BREAKING) and the exit code mapping (0/2/4 for compare, 0/1/2 for compat) are implemented in checker_policy.py and cli.py.

Key decisions embedded in code: - COMPATIBLE_WITH_RISK is a distinct tier (not folded into COMPATIBLE or API_BREAK) - Exit code 4 (not 1) signals binary ABI break — allows bitwise OR composition - Unknown ChangeKind values default to BREAKING (fail-safe) - Verdict is computed on the full change set regardless of display filters - compat command uses a different exit code scheme than compare

Why it needs an ADR: - External tools and CI pipelines depend on the exit code contract - The COMPATIBLE_WITH_RISK tier is a novel design choice not found in ABICC or libabigail - The fail-safe default for unknown kinds is a safety-critical design decision - Different exit codes for compare vs compat needs explicit justification


Candidate ADR 2: Policy Profile System (strict_abi / sdk_vendor / plugin_abi)

Urgency: HIGH

What exists: Three built-in policy profiles with explicit downgrade sets in checker_policy.py:466–509. Custom YAML policy files in policy_file.py. The policy_kind_sets() function is declared "the single source of truth."

Key decisions embedded in code: - strict_abi is the default (maximum severity for all changes) - sdk_vendor downgrades source-level-only API_BREAK kinds to COMPATIBLE - plugin_abi downgrades calling-convention BREAKING kinds to COMPATIBLE AND promotes RISK_KINDS to BREAKING - plugin_abi has unique semantics: deployment-floor risks become BREAKING - Custom YAML policies override individual ChangeKind severity - Import-time assertions enforce that downgrade sets are proper subsets

Why it needs an ADR: - Policy profiles fundamentally change what constitutes a "break" — users must understand the trade-offs - The plugin_abi RISK→BREAKING promotion is non-obvious and needs architectural rationale - Custom policy files create a user-facing contract (YAML schema) - Interaction between --policy and --policy-file needs formal specification


Candidate ADR 3: ABI Change Classification Taxonomy (114 ChangeKinds)

Urgency: MEDIUM

What exists: 85+ ChangeKind enum values in checker_policy.py, each classified into exactly one of BREAKING_KINDS, API_BREAK_KINDS, COMPATIBLE_KINDS, or RISK_KINDS. Many classifications have inline comments justifying the choice.

Key decisions embedded in code: - ENUM_MEMBER_ADDED → COMPATIBLE (not BREAKING), because value shifts are caught separately - FUNC_NOEXCEPT_ADDED/REMOVED → COMPATIBLE (not BREAKING), because Itanium ABI mangling doesn't change - SYMBOL_BINDING_CHANGED (GLOBAL→WEAK) → COMPATIBLE (not BREAKING) - FUNC_REMOVED_ELF_ONLY → COMPATIBLE (visibility cleanup heuristic) - TYPE_FIELD_ADDED → BREAKING for polymorphic types, TYPE_FIELD_ADDED_COMPATIBLE for standard-layout - FIELD_BECAME_CONST etc. → COMPATIBLE (field qualifiers are informational) - FUNC_BECAME_INLINE → API_BREAK (not BREAKING — symbol may or may not vanish) - TYPEDEF_VERSION_SENTINEL → COMPATIBLE (version-stamped typedefs are compile-time only) - SYMBOL_LEAKED_FROM_DEPENDENCY_CHANGED → COMPATIBLE_WITH_RISK (dependency origin detection)

Why it needs an ADR: - Some classifications diverge from ABICC/libabigail (intentionally) — documenting why prevents regressions - The noexcept and enum_member_added decisions are contentious in the ABI community - ADR-001 documents a few classifications inline but the full taxonomy deserves its own record - Contributors adding new ChangeKinds need a classification framework, not just examples

Note: ADR-001 already contains NEEDED_ADDED, SYMBOL_BINDING_STRENGTHENED, and SYMBOL_SIZE_CHANGED classification rationale. A new ADR should consolidate and extend these.


Candidate ADR 4: ABICC Drop-In Compatibility Layer

Urgency: MEDIUM

What exists: abicheck/compat/cli.py (1328 lines) and abicheck/compat/xml_report.py (398 lines) implement a full ABICC-compatible CLI and XML report format. The abicheck compat command accepts ABICC-style inputs (XML descriptors, version strings).

Key decisions embedded in code: - Separate compat subcommand (not CLI flag on compare) - ABICC XML descriptor parsing (old_descriptor.xml → library path + headers + version) - ABICC XML report generation for backward-compatible output - ABICC suppression file format support (skip_symbols / skip_headers / skip_types) - Different exit codes: compat uses 0/1/2 vs compare uses 0/2/4 - Format-detection heuristic: Perl data dump files from ABICC are auto-detected

Why it needs an ADR: - ABICC compatibility is a stated project goal (Goal 1) but the architecture is undocumented - The decision to use a separate compat command vs extending compare has implications - The XML report format is a compatibility contract - Exit code differences between compare and compat need justification


Candidate ADR 5: Suppression System Design

Urgency: MEDIUM

What exists: suppression.py (291 lines) implements YAML-based suppression rules with symbol/type/version/platform/scope filters, Python re-based pattern matching with fullmatch semantics, and expiry dates.

Key decisions embedded in code: - YAML format (not JSON or TOML) for suppression files - Python re module with fullmatch() semantics (see ADR-013 for ReDoS considerations) - Suppression applied before redundancy filtering (ordering matters for verdicts) - Both abicheck-native YAML and ABICC skip/whitelist formats supported - Suppressed changes tracked in DiffResult.suppressed_changes for audit trail - Entry-level validation: unknown keys rejected, expired entries warned

Why it needs an ADR: - Suppression ordering relative to other pipeline stages affects correctness - Regex engine choice is a security decision that should be formally documented - Dual-format support (YAML + ABICC format) adds maintenance burden — needs justification - Expiry dates on suppressions is a novel feature not in reference tools


Candidate ADR 6: Output Format Strategy (Markdown / JSON / SARIF / HTML)

Urgency: MEDIUM

What exists: Four output formats implemented across reporter.py, sarif.py, html_report.py. SARIF 2.1.0 is specifically targeted for GitHub Code Scanning integration.

Key decisions embedded in code: - Markdown is the default format (human-readable, CI-friendly) - JSON output uses AbiSnapshot schema with schema_version field (currently v3) - SARIF maps ChangeKind → SARIF rule IDs and severity → SARIF level - HTML is self-contained (embedded CSS, no external dependencies) - All formats preserve the same information (no format-specific data loss) - JSON snapshots are interchangeable regardless of data source (DWARF vs castxml)

Why it needs an ADR: - schema_version is a versioned contract — backward compatibility rules need definition - SARIF mapping decisions (which severity maps to which SARIF level) affect GitHub Code Scanning UX - The decision to embed CSS in HTML (vs external stylesheet) affects deployment


Candidate ADR 7: MCP Server Integration (AI Agent Interface)

Urgency: LOW

What exists: mcp_server.py (750+ lines) exposes abicheck as a FastMCP server for AI agents. Separate entry point abicheck-mcp.

Key decisions embedded in code: - MCP is an optional dependency (pip install abicheck[mcp]) - FastMCP framework chosen (over raw MCP protocol implementation) - Stdio transport by default - Tools exposed: dump, compare, explain-change, list-change-kinds - Graceful ImportError with helpful message if mcp package not installed

Why it needs an ADR: - MCP is a relatively new protocol — commitment to it as an integration path should be documented - The tool surface area (which operations are exposed) is a design decision - Optional dependency strategy affects distribution and testing


Candidate ADR 8: Snapshot Serialization and Schema Versioning

Urgency: MEDIUM

What exists: serialization.py (490 lines) with SCHEMA_VERSION = 3, explicit version history comments, and forward/backward compatibility handling.

Key decisions embedded in code: - Schema version is an integer (not semver) - v1 snapshots (pre-versioning) are handled transparently - Sets are converted to sorted lists for JSON determinism - AbiSnapshot is the canonical interchange format between dump/compare/report stages - Snapshots are platform-agnostic (ELF/PE/Mach-O metadata fields are all optional) - dataclasses.asdict() is used with post-processing (not custom serialization)

Why it needs an ADR: - Schema version bumps are breaking changes for snapshot consumers - The decision to use integer versioning (not semver) needs rationale - Cross-mode snapshot comparison (DWARF snapshot vs castxml snapshot) relies on schema equivalence


Candidate ADR 9: Three-Tier Visibility Model (PUBLIC / HIDDEN / ELF_ONLY)

Urgency: MEDIUM

What exists: Visibility enum in model.py with three values. ELF_ONLY is used throughout detection as a confidence/provenance tier.

Key decisions embedded in code: - ELF_ONLY is not a visibility attribute but a detection confidence indicator - FUNC_REMOVED_ELF_ONLY is COMPATIBLE (not BREAKING) — treats symbol-only removals as visibility cleanup - Visibility affects how changes are classified (different ChangeKinds for ELF_ONLY vs PUBLIC) - In DWARF-only mode, visibility is determined by intersection with ELF exported symbols

Why it needs an ADR: - This is a novel concept not in ABICC or libabigail - The conflation of "source of data" with "visibility" in a single enum is a design choice - The verdict difference between FUNC_REMOVED (BREAKING) and FUNC_REMOVED_ELF_ONLY (COMPATIBLE) is significant


Candidate ADR 10: GitHub Action Design

Urgency: LOW

What exists: action.yml with multiple operation modes (compare, dump, deps, stack-check), support for ABICC Perl dumps, and PR comment integration.

Key decisions embedded in code: - Composite action (not Docker-based) — runs in the user's environment - castxml installed via apt in the action setup step - Multiple operation modes exposed through a single mode input - Supports both native abicheck inputs and ABICC migration inputs - PR comment posting with diff summary

Why it needs an ADR: - Composite vs Docker action affects portability (Linux-only for apt) - The castxml installation strategy affects action startup time and reliability - Supporting ABICC inputs in the action adds complexity for migration use case


Candidate ADR 11: Cross-Platform Binary Format Support Strategy

Urgency: LOW

What exists: Separate metadata modules: elf_metadata.py, pe_metadata.py, macho_metadata.py. Platform detection in model.py. PDB parser (pdb_parser.py, pdb_metadata.py) for Windows debug info.

Key decisions embedded in code: - Each platform has an independent metadata module (no shared base class) - PDB support is "minimal" — custom parser, not a full PDB implementation - PDB metadata is adapted to DWARF structures (StructLayout, FieldInfo) - Mach-O compat_version is treated as equivalent to ELF SONAME for versioning - Platform is detected from binary content, not from file extension

Why it needs an ADR: - ADR-001 lists the libraries but doesn't document the adapter pattern or PDB strategy - The decision to write a custom PDB parser (vs depending on an external tool) is significant - Platform-specific ChangeKinds (e.g., COMPAT_VERSION_CHANGED) need justification


Candidate ADR 12: Testing Strategy and Parity Validation

Urgency: LOW

What exists: 120+ test files, 4 test tiers (unit, integration, parity-abicc, parity-libabigail), 63 example cases, conditional CI gates.

Key decisions embedded in code: - 80% coverage threshold enforced in CI - Parity tests are gated on file-change detection (not run on every PR) - Example cases serve as both documentation and regression tests - ABICC and libabigail are test-only dependencies (not runtime) - Test markers (@pytest.mark.integration etc.) control what runs where

Why it needs an ADR: - The parity testing strategy is central to project credibility - The 80% threshold is a policy decision - Conditional gating logic affects CI reliability


Summary and Prioritization

Note: This gap analysis was created as a point-in-time snapshot. ADRs 009–019 have since been written and accepted, covering candidates 1–6 and 8–12. Only candidate 7 (MCP Server Integration) remains undocumented.

# Candidate ADR Urgency Status
1 Verdict System and Exit Code Contract HIGH Documented → ADR-009
2 Policy Profile System HIGH Documented → ADR-010
3 ABI Change Classification Taxonomy MEDIUM Documented → ADR-011
4 ABICC Drop-In Compatibility Layer MEDIUM Documented → ADR-012
5 Suppression System Design MEDIUM Documented → ADR-013
6 Output Format Strategy MEDIUM Documented → ADR-014
7 MCP Server Integration LOW Not documented
8 Snapshot Serialization and Schema Versioning MEDIUM Documented → ADR-015
9 Three-Tier Visibility Model MEDIUM Documented → ADR-016
10 GitHub Action Design LOW Documented → ADR-017
11 Cross-Platform Binary Format Support LOW Documented → ADR-018
12 Testing Strategy and Parity Validation LOW Documented → ADR-019

Remaining gap

Only candidate 7 (MCP Server Integration) lacks a formal ADR. This is low urgency since MCP is an optional feature with a small user surface.

Existing ADR status review

ADR Title Status Assessment
001 Technology Stack Accepted Good but overloaded — contains classification decisions that belong in ADR-011
002 Multi-binary Release Compare Accepted Implemented in v0.2.0 (compare-release command)
003 Data Source Architecture Accepted Implemented — DwarfSnapshotBuilder landed in v0.2.0
004 Report Filtering and Deduplication Accepted Implemented in v0.2.0 (--show-only, --stat, --report-mode leaf)
005 Application Compatibility Checking Accepted Implemented in v0.2.0 (appcompat command)
006 Package-Level Comparison Accepted Implemented in v0.2.0 (RPM/Deb/tar/conda/wheel extraction)
007 BTF and CTF Debug Format Support Proposed Future work — well-specified
008 Full-Stack Dependency Validation Accepted Implemented in v0.2.0 (deps, stack-check commands)