Commit graph build-cran-binaries/.crow/rebuild-abi-gap.yaml
Author SHA1 Message Date
26406a3e86 feat(ci): add a targeted per-R-minor rebuild workflow (#198)
All checks were successful
ci/crow/manual/rebuild-abi-gap/7 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/9 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/11 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/13 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/17 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/2 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/1 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/15 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/3 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/6 Pipeline was successful
ci/crow/cron/process-updates/11 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/5 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/8 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/10 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/12 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/14 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/16 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/18 Pipeline was successful
ci/crow/cron/process-updates/18 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/4 Pipeline was successful
## Why

A user hit this on amd64/resolute under R 4.6.1:

```
base64enc.so: undefined symbol: SETLENGTH
```

R 4.6 no longer exports `SETLENGTH`:

```
R 4.4: missing -> R_ClosureEnv R_ClosureFormals R_MakeMissingBinding R_PromiseEnv R_PromiseValue R_mkClosure
R 4.5: missing -> R_MakeMissingBinding R_PromiseEnv R_PromiseValue
R 4.6: missing -> R_PromiseEnv R_PromiseValue SETLENGTH
```

`base64enc` calls `SETLENGTH` (`base64.c:206`, `253`), its flat binary was built under R 4.4.3, and it has no per-minor copy at all, so the union index hands that binary to a 4.6 client.

The safety net missed it because the risky set is defined circularly:

```r
risky_packages_across_minors <- function(...) {
  # a package is "risky" iff it already has a tarball in some per-minor slot
```

A package counts as ABI-risky only if it has *already* been built per-minor. `base64enc` never was, and having no new CRAN version it never enters the build list to be classified. So the per-minor protection only covers packages that passed through the pipeline after the scheme existed.

## Scope

Scanning all 4,585 compiled binaries in amd64/resolute's flat index for symbols R 4.6 does not export found 10 referencing `SETLENGTH`. Most already have per-minor builds and are served correctly. Two do not:

```
base64enc_0.1-6   flat=200  4.4=404  4.5=404  4.6=404
lobstr_1.2.1      flat=200  4.4=404  4.5=200  4.6=404
```

(`iotools` lacks a 4.5 copy but 4.5 still exports `SETLENGTH`, so it does not break.)

## What this adds

Rebuilding one package into one minor was only possible with `just rebuild` from a workstation holding the S3 and Postgres credentials. This adds the CI path:

```sh
crow pipeline create devxy/build-cran-binaries \
  --var rebuild_abi_gap=ubuntu-2604-amd64 \
  --var ABI_PACKAGES="base64enc@0.1-6 lobstr@1.2.1" \
  --var ABI_R_MINOR=4.6
```

- reuses `reindex.yaml`'s slot matrix verbatim, so the slot-to-image mapping cannot drift
- runs `build-one.R --sensitive-only`, which reclassifies each version and skips anything not ABI-risky rather than writing it to the wrong slot
- mounts no shared cache volume, so it cannot disturb the build pipeline's per-agent caches
- attempts every package, then exits non-zero if any failed

`crow lint` passes.

## Not in this PR

This is the remediation path, not the fix. The circular `risky_packages_across_minors()` definition is still in place, so new instances of this class can still appear. That is the follow-up.

Reviewed-on: #198
2026-09-04 10:24:47 +00:00