test(verify): fail a slot still serving an ABI-risky binary of another minor

The comprehensive production sweep passed 236/0 while 2608 records were
being served that crash at load: the exact failure this whole effort was
about. Nothing in the suite looked for them.

The guard in bincraft drops them at index time, so it only takes effect
where a reindex has run since it shipped. amd64/resolute is clean;
fourteen slots are not, and no check said so.

A package carrying a Path in any per-minor index is risky by
construction, which is the signal for the ones absent from this minor's
index. Serving such a package from the generic slot with a binary built
under a different minor is the crash; that count must be zero.
This commit is contained in:
Patrick Schratz 2026-08-31 12:58:58 +00:00
commit 0529b55254
No known key found for this signature in database
GPG key ID: 62050D5BC68AB6DC

View file

@ -140,6 +140,23 @@ fallback_counts() {
'
}
# Packages this index serves from the generic slot with a binary built under a
# different R minor, while some other per-minor slot carries a build of them -
# which proves the ABI classifier called them risky. Serving those is the
# load-time crash the per-minor slots exist to prevent. bincraft drops them at
# index time, so a non-zero count means the slot has not been reindexed since
# that guard shipped.
abi_unsafe_count() {
local minor_file=$1 minor=$2 risky_file=$3
gunzip -c "$minor_file" 2>/dev/null | awk -v m="$minor" '
/^Package:/ { pkg = $2; path = ""; built = "" }
/^Path:/ { path = $2 }
/^Built:/ { built = $2 " " $3 }
/^$/ { if (pkg != "" && path == "" && built != "" && built !~ ("^R " m "\\.")) print pkg; pkg = "" }
' | sort -u > "$minor_file.mismatched"
comm -12 "$minor_file.mismatched" "$risky_file" | wc -l
}
# How many packages a client of <minor> would receive as source through
# per-minor routing while the generic slot holds a binary built under that very
# minor. Zero is the bar for enabling a slot.
@ -327,6 +344,30 @@ for arch in $ARCHES; do
fi
fi
# Packages carrying a Path in any per-minor index are risky by construction.
: > "$WORK/${arch}-${distro}.risky"
for minor in $MINORS; do
f="$WORK/${arch}-${distro}-${minor}.gz"
[ -s "$f" ] || continue
gunzip -c "$f" 2>/dev/null | awk '
/^Package:/ { pkg = $2; path = "" }
/^Path:/ { path = $2 }
/^$/ { if (pkg != "" && path != "") print pkg; pkg = "" }
' >> "$WORK/${arch}-${distro}.risky"
done
sort -u -o "$WORK/${arch}-${distro}.risky" "$WORK/${arch}-${distro}.risky"
for minor in $MINORS; do
f="$WORK/${arch}-${distro}-${minor}.gz"
[ -s "$f" ] || continue
unsafe=$(abi_unsafe_count "$f" "$minor" "$WORK/${arch}-${distro}.risky")
if [ "${unsafe:-0}" -gt 0 ]; then
bad "$slot R $minor serves $unsafe ABI-risky package(s) built under another R minor - reindex this slot"
else
ok "$slot R $minor serves no ABI-risky package from another minor"
fi
done
# Excluded minors: no published index, and under --live a redirect to CRAN.
for minor in $EXCLUDED_MINORS; do
ex_url="$BASE/$slot/latest/src/contrib/$minor/PACKAGES.gz"