Commit graph build-cran-binaries/.crow/prune-stale-flat-binaries.yaml
Author SHA1 Message Date
416a2d64d9 chore(alpine): drop the deprecated Alpine 3.22 slot (#213)
All checks were successful
ci/crow/manual/verify-repo-routing/11 Pipeline was successful
ci/crow/manual/verify-repo-routing/13 Pipeline was successful
ci/crow/manual/verify-repo-routing/2 Pipeline was successful
ci/crow/manual/verify-repo-routing/15 Pipeline was successful
ci/crow/manual/verify-repo-routing/1 Pipeline was successful
ci/crow/manual/verify-repo-routing/3 Pipeline was successful
ci/crow/manual/verify-repo-routing/4 Pipeline was successful
ci/crow/manual/verify-repo-routing/7 Pipeline was successful
ci/crow/manual/verify-repo-routing/12 Pipeline was successful
ci/crow/manual/verify-repo-routing/8 Pipeline was successful
ci/crow/manual/verify-repo-routing/9 Pipeline was successful
ci/crow/manual/verify-repo-routing/14 Pipeline was successful
ci/crow/manual/verify-repo-routing/10 Pipeline was successful
ci/crow/manual/verify-repo-routing/16 Pipeline was successful
ci/crow/manual/verify-repo-routing/5 Pipeline was successful
ci/crow/manual/verify-repo-routing/6 Pipeline was successful
ci/crow/cron/process-updates/1 Pipeline was successful
ci/crow/cron/process-updates/2 Pipeline was successful
ci/crow/cron/process-updates/8 Pipeline was successful
ci/crow/cron/process-updates/12 Pipeline was successful
ci/crow/cron/process-updates/14 Pipeline was successful
ci/crow/cron/process-updates/11 Pipeline was successful
ci/crow/cron/process-updates/16 Pipeline was successful
ci/crow/cron/process-updates/9 Pipeline was successful
ci/crow/cron/process-updates/13 Pipeline was successful
ci/crow/cron/process-updates/10 Pipeline was successful
ci/crow/cron/process-updates/15 Pipeline was successful
ci/crow/cron/process-updates/4 Pipeline was successful
ci/crow/cron/process-updates/3 Pipeline was successful
## Why

Alpine 3.22 is deprecated and should be dropped.

It was never a published slot to begin with.
`local.rpkgs_slots` in `cdn.tf` enumerates eight distros and alpine322 is not one of them, so `UNION_SLOTS` never gated per-minor rewriting for it.
The website's `DistroId` union lists the same eight.
Every request to the slot therefore fell through to the flat index, which holds R 4.5 builds.

## Evidence

The fleet-wide routing verification (pipeline 12591, `verify_routing=all`) measured this end to end.

Across all 16 published slots, both architectures, three R minors and both contractual URL forms: **1056 of 1056 install-and-load checks passed.**

Every failure in the run was alpine322, identical on amd64 and arm64:

```
amd64/alpine322 R 4.4 via arch|generic|flat:  8 of 11
amd64/alpine322 R 4.6 via arch|generic|flat: 10 of 11
arm64/alpine322 R 4.4 via arch|generic|flat:  8 of 11
arm64/alpine322 R 4.6 via arch|generic|flat: 10 of 11
```

The tell is that all three forms agree.
On a routed slot `arch` and `generic` diverge from `flat`, because that divergence *is* the routing.
On alpine322 there is nothing to diverge from.
R 4.5 clients were 11 of 11 on every form, because flat happens to be what they need.

The data itself is not broken, only unreachable.
`alpine322/contrib/4.4/data.table_1.18.6.1.tar.gz` is genuinely `Built: R 4.4.3` and the 4.6 object is `Built: R 4.6.0`.
Correct objects, correct per-minor indexes, never served.

## What changed

- alpine-322 matrix rows and dropdown options removed from the 12 pipelines that carried them, so `=all` no longer fans out to a slot nobody can reach.
- `3.22` removed from the `OS_VERSION` dropdowns in `build-all-versions.yaml` and `repair-built-stamp.yaml`; the latter's default moves to `3.23`.
- Stale `3.22` examples updated in `local/packages-to-build.R`.

`crow lint .crow/` reports all 18 configs valid.

## Also done outside this diff

The six alpine-322 cron jobs are deleted from the Crow server, since a cron whose matrix row is gone keeps firing against nothing:

```
5   process-cran-updates-alpine-322-amd64     0 5/8 * * *
6   process-cran-updates-alpine-322-arm64     30 5/8 * * *
68  weekly-audit-missing-alpine-322-amd64     20 0 * * 0
69  weekly-audit-missing-alpine-322-arm64     30 0 * * 0
82  weekly-rebuild-missing-alpine-322-amd64   0 4 * * 0
83  weekly-rebuild-missing-alpine-322-arm64   10 4 * * 0
```

They are listed here so they can be recreated if this is ever reversed.

## Not done

The published objects and indexes under `amd64/alpine322` and `arm64/alpine322` are left in the bucket.
Deleting them is a separate decision, and nothing routes to them in the meantime.

Reviewed-on: #213
2026-09-11 07:27:03 +00:00
a84cca1949 fix(abi): prune the objects recorded broken, not every risky package (#211)
## Why

A dry run of the prune on `ubuntu-2404-amd64` (pipeline 12494):

```
Dry run: 3013 object(s) left in place. Re-run with --apply to delete.
```

3013 objects on one slot. The recorded verdicts say roughly **230** of that slot's objects actually fail `dyn.load()` anywhere.

The gap is the selector: the prune used `abi_cache_risky_packages()`, the source classifier, which calls **93%** of compiled packages risky where the binaries' own symbols say **4.3%**. Applying it would have deleted ~2800 working binaries and turned each into a source fallback for every client that cannot be routed by R minor.

This is the same mistake #129 fixed in the index writer. The prune was written before the per-object verdicts existed (#200, before #128/#129) and was never brought forward.

## What changed

- Selection comes from `abi_flat_safety`, every `NOT safe` row for the slot regardless of which minor it fails under, since the flat object is served to clients on all of them.
- Matching is by **object**, `<package>_<version>`, not by package name. A package can have a safe flat binary at one version and an unsafe one at another; the old code deleted both.
- The explicit `[pkg ...]` override is unchanged in behaviour and still matches by package name, which is what you want when naming a package by hand.
- The refusal guard moves with the selector. An empty result now means the slot's flat-safety backfill never ran — the exact state rhel8 was found in today — and pruning nothing while reporting success would have hidden it.

## Not changed

Still a dry run unless `PRUNE_APPLY=yes`, and still refuses to delete an object whose package CRAN no longer ships, since there would be no source to fall back to.

## Ordering note

A prune converts each deleted binary into a source fallback. bincraft #131 fixes source fallbacks of *archived* versions being advertised as binaries, which breaks `uvr` outright. That should land before any large prune, or this will manufacture new instances of it.

Reviewed-on: #211
2026-09-09 15:01:21 +00:00
f9ebb3dba4 fix(abi): prune flat-slot binaries of ABI-risky packages (#200)
Some checks failed
ci/crow/cron/process-updates/13 Pipeline was successful
ci/crow/manual/reindex/1 Pipeline was successful
ci/crow/manual/reindex/3 Pipeline was successful
ci/crow/manual/reindex/9 Pipeline was successful
ci/crow/manual/reindex/5 Pipeline was successful
ci/crow/manual/reindex/13 Pipeline was successful
ci/crow/manual/reindex/15 Pipeline was successful
ci/crow/manual/reindex/11 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/4 Pipeline was successful
ci/crow/cron/process-updates/15 Pipeline was successful
ci/crow/cron/process-updates/2 Pipeline was successful
ci/crow/cron/process-updates/11 Pipeline was successful
ci/crow/cron/process-updates/8 Pipeline was successful
ci/crow/manual/rebuild-abi-gap/6 Pipeline was successful
ci/crow/cron/process-updates/17 Pipeline was successful
ci/crow/manual/reindex/2 Pipeline was successful
ci/crow/manual/reindex/4 Pipeline was successful
ci/crow/manual/reindex/8 Pipeline failed
ci/crow/manual/reindex/6 Pipeline was successful
ci/crow/manual/reindex/10 Pipeline was successful
ci/crow/manual/reindex/12 Pipeline was successful
ci/crow/manual/reindex/14 Pipeline was successful
ci/crow/manual/reindex/16 Pipeline was successful
ci/crow/manual/reindex/18 Pipeline was successful
ci/crow/cron/process-updates/10 Pipeline was successful
ci/crow/cron/process-updates/4 Pipeline was successful
ci/crow/cron/process-updates/16 Pipeline was successful
ci/crow/cron/process-updates/14 Pipeline was successful
ci/crow/cron/process-updates/12 Pipeline was successful
ci/crow/cron/process-updates/18 Pipeline was successful
## Why

`base64enc` still fails to install under R 4.6 even after it was built into the 4.6 slot:

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

The per-minor binary is correct and serves correctly to any client the router can identify. But the **flat slot still holds a binary built under R 4.4.3**, and that is what every client receives when it cannot be routed by R minor.

That object should not exist. `upload_single_binary()` writes an ABI-risky package only to the per-minor path:

```r
if (is_r_minor_sensitive) {
  remote_tarball_path <- file.path(remote_bin_path, minor_version, tarball_name)
} else {
  remote_tarball_path <- file.path(remote_bin_path, tarball_name)
}
```

`base64enc`'s flat binary is a legacy artifact from before it was classified risky. Removing it makes the outcome correct regardless of routing, user agent, downloader or renv configuration, which is the part I could not otherwise guarantee.

## What it does

`local/prune-stale-flat-binaries.R` lists a slot's flat objects, intersects them with the risky set, and deletes them. Deleting is safe here: the source fallback republishes the CRAN source under the same key, so the package stays installable everywhere and compiles against the client's own R, while routed clients keep getting the per-minor binary.

Safety posture, given this deletes production objects:

- **dry run by default**; nothing is removed without `--apply`
- takes an **explicit package list**, so the first runs need not trust the classifier cache at all
- **refuses to proceed on an empty risky set**, which is indistinguishable from an unreachable cache and would otherwise report success having pruned nothing
- **keeps objects whose package CRAN no longer ships**, since there would be no source to fall back to and the package would vanish
- **re-indexes and purges in the same run**. This is not optional: a published index naming deleted files serves 404s, which is worse than the binary it replaced.

## Use

```sh
# dry run first
crow pipeline create devxy/build-cran-binaries \
  --var prune_flat=ubuntu-2604-amd64 --var PRUNE_PACKAGES="base64enc lobstr"

# then apply
crow pipeline create devxy/build-cran-binaries \
  --var prune_flat=ubuntu-2604-amd64 --var PRUNE_PACKAGES="base64enc lobstr" \
  --var PRUNE_APPLY=yes
```

`crow lint` passes.

## Sequencing

Run with an explicit list first, on one slot, and confirm the flat URL then serves source while the per-minor URLs still serve binaries. Only widen to the cache-derived set once the backfill has finished and its output looks sane: the first 250 classifications came back 232 r-minor-sensitive, and a rate that high suggests the source-grep classifier is broader than the set that actually breaks, which is worth checking before pruning thousands of flat binaries.

Reviewed-on: #200
2026-09-04 10:54:09 +00:00