feat(abi): backfill classifier verdicts for pre-per-minor packages #199

Merged
pat-s merged 1 commit from feat/backfill-abi-classification into main 2026-09-04 10:34:50 +00:00
Owner

Why

bincraft PR #124 makes the abi_classification cache authoritative for the risky-package set, breaking the circularity where a package counted as ABI-risky only once it had already been built per-minor.

On its own that changes nothing for the packages actually at risk. The cache only holds verdicts for packages that passed through the build pipeline after the per-minor scheme existed; everything older has no verdict, and no verdict means the flat binary is carried into every per-minor index. That is exactly how a base64enc built under R 4.4.3 reached an R 4.6.1 client:

base64enc.so: undefined symbol: SETLENGTH

This is the backfill that gives those packages a verdict.

What it does

local/backfill-abi-classification.R enumerates a slot's flat index and runs each compiled package through bincraft::classify_r_minor_sensitive(), which looks the verdict up in the cache, and on a miss downloads the CRAN source, classifies it and stores the result.

The verdict is a property of the source, independent of OS, arch and R minor, so one run populates the cache for every slot. The arch/codename arguments only choose which flat index to enumerate.

Only NeedsCompilation=yes packages are classified: a package with no compiled code has no symbols to bind and cannot fail this way, so proving it by download would be wasted work. On amd64/resolute that is 4585 packages rather than 23218.

Re-running is cheap and resumable: cached verdicts return without touching the network, so a second run costs one query per package and downloads only the remainder.

Verification

The enumeration was run against the live indexes and matches an independent symbol scan of the same slot exactly:

flat binaries: 23218 | due classification: 4585
base64enc included: TRUE
lobstr included:    TRUE

4585 is the same population I scanned with nm -D --undefined-only when establishing the blast radius, and both packages confirmed to break under 4.6 are in it.

crow lint passes.

Sequencing

Merge after bincraft #124 and after a bincraft release the pipelines pick up, otherwise the verdicts are written but nothing reads them yet. Writing them early is harmless.

Then re-index the slots so the per-minor indexes are rebuilt with the widened risky set. Expect coverage in the per-minor indexes to drop slightly as newly-recognised risky packages are dropped rather than served from a mismatched minor; those then need per-minor builds, either through the normal pipeline or the targeted workflow in devxy/build-cran-binaries#198.

## Why bincraft PR #124 makes the `abi_classification` cache authoritative for the risky-package set, breaking the circularity where a package counted as ABI-risky only once it had already been built per-minor. On its own that changes nothing for the packages actually at risk. The cache only holds verdicts for packages that passed through the build pipeline after the per-minor scheme existed; everything older has no verdict, and no verdict means the flat binary is carried into every per-minor index. That is exactly how a `base64enc` built under R 4.4.3 reached an R 4.6.1 client: ``` base64enc.so: undefined symbol: SETLENGTH ``` This is the backfill that gives those packages a verdict. ## What it does `local/backfill-abi-classification.R` enumerates a slot's flat index and runs each compiled package through `bincraft::classify_r_minor_sensitive()`, which looks the verdict up in the cache, and on a miss downloads the CRAN source, classifies it and stores the result. The verdict is a property of the source, independent of OS, arch and R minor, so **one run populates the cache for every slot**. The arch/codename arguments only choose which flat index to enumerate. Only `NeedsCompilation=yes` packages are classified: a package with no compiled code has no symbols to bind and cannot fail this way, so proving it by download would be wasted work. On amd64/resolute that is 4585 packages rather than 23218. Re-running is cheap and resumable: cached verdicts return without touching the network, so a second run costs one query per package and downloads only the remainder. ## Verification The enumeration was run against the live indexes and matches an independent symbol scan of the same slot exactly: ``` flat binaries: 23218 | due classification: 4585 base64enc included: TRUE lobstr included: TRUE ``` 4585 is the same population I scanned with `nm -D --undefined-only` when establishing the blast radius, and both packages confirmed to break under 4.6 are in it. `crow lint` passes. ## Sequencing Merge after bincraft #124 and after a bincraft release the pipelines pick up, otherwise the verdicts are written but nothing reads them yet. Writing them early is harmless. Then re-index the slots so the per-minor indexes are rebuilt with the widened risky set. Expect coverage in the per-minor indexes to *drop* slightly as newly-recognised risky packages are dropped rather than served from a mismatched minor; those then need per-minor builds, either through the normal pipeline or the targeted workflow in devxy/build-cran-binaries#198.
bincraft now unions the ABI classifier cache into its risky-package set, but the
cache only holds verdicts for packages that passed through the build pipeline
after the per-minor scheme existed. Everything older has no verdict, and no
verdict means the flat binary is carried into every per-minor index. That is the
population the fix has to reach: a base64enc built under R 4.4.3 was served to
R 4.6 clients, which have no SETLENGTH to bind.

The verdict is computed from the package source, so it is independent of OS,
arch and R minor: one run populates the cache for every slot.

Only NeedsCompilation=yes packages are classified. A package with no compiled
code has no symbols to bind and cannot fail this way, which cuts the work from
23218 packages to 4585 on amd64/resolute.

This change will:

- add local/backfill-abi-classification.R, enumerating a slot's flat index and
  classifying each compiled package through bincraft's cached classifier
- add a manual .crow workflow to run it
- stay resumable, since cached verdicts return without touching the network, so
  a re-run only downloads what is still unclassified
pat-s merged commit 853b924765 into main 2026-09-04 10:34:50 +00:00
pat-s deleted branch feat/backfill-abi-classification 2026-09-04 10:34:50 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
devxy/build-cran-binaries!199
No description provided.