Skip to content

A large export shrink with no declared surface emits no finding in header mode (2026-10-22)

Index: Known gaps

Status (2026-10-24): FIXED — the reported failure does not reproduce; the invariant is now pinned. The 2026-10-22 analysis below was wrong about header mode. compare.undeclared_exports._export_only_removals (added in #1308) already emits func_removed_elf_only/var_removed_elf_only for every export that OLD carried, NEW lost, and no public header declares, when both sides are header-aware. Without --contract these are BREAKING, as ADR-063's 2026-10-01 amendment requires ("an undeclared export's removal is still a break", catalog case182). With --contract public they stay listed in changes, counted under disposition_audit.counts.unresolved_relevance with contract_reason_code: closed_domain_no_commitment (ADR-067: recorded before disposition, reason kept). Pinned for shrink sizes 2→1 up to 281→138, C and C++-mangled names, functions and data, with and without a version script, and headers declaring none or some of the exports by tests/test_undeclared_export_shrink.py and tests/test_undeclared_export_shrink_integration.py (gcc + castxml through the CLI JSON), bug class detector.undeclared_export_shrink_silent. Not changed: ABI_SURFACE_EXPLOSION stays growth-only, and no aggregate shrink kind exists.

Original 2026-10-22 write-up (kept for history):

abi_surface_explosion used to fire for a large export-count shrink (a 281 to 138 shrink was the field report), under the growth kind and with the growth advice. It is now growth-only (diff_platform._diff_abi_surface), so a shrink is no longer reported under that kind.

For an ELF-only dump the removals are still reported one by one: the dumper builds an export-table Function for each symbol, and the functions detector classifies each removal as func_removed_elf_only (tests/test_abi_surface_direction.py pins 143 such findings for 281 to 138).

For a header-mode comparison, the exports that no header declares are not functions entries. Removing them emits no change, so a shrink of undeclared exports now reads as no finding at all. The old aggregate was the only signal for that case, and it is gone.

Whether undeclared exports belong in the compatibility contract is a question for the surface-declaration model (surface_facts), not for this detector. The choice is one of: report the shrink under a direction-correct kind, or record undeclared-export removals as their own evidence. Neither is implemented.

Do not restore the shrink under ABI_SURFACE_EXPLOSION with a different description without an ADR: that renames the meaning of a public kind.