feat(abi): backfill per-object flat-slot safety verdicts #202

Merged
pat-s merged 1 commit from feat/backfill-flat-object-safety into main 2026-09-05 09:35:47 +00:00
Owner

Why

bincraft #128 lets union_index_records() keep a carried flat record when that specific object is recorded safe. On its own it changes nothing for the records already being dropped, because they are legacy objects with no verdict:

4.4: 16398 dropped across 9 indexes  (~1822 each)
4.5: 20076 dropped across 9 indexes  (~2230 each)
4.6: 15532 dropped across 9 indexes  (~1726 each)

Every one of those resolves to CRAN source for clients the routing serves correctly. This backfill inspects the objects already in the bucket and writes their verdicts, which is what actually restores the coverage.

How the verdict is made

By symbols, not heuristics. A binary fails at dyn.load() under minor M exactly when it references a symbol M does not export, and the volatile set is derived by diffing the libR.so exports of the R builds installed in the image — so a new R release updates it with no curated list to maintain.

That matters here because the source-level classifier calls 93% of compiled packages risky (LinkingTo: Rcpp alone is enough), while a symbol scan of the same slot found 17 packages that break under 4.6 and 72 under 4.4. Recording the real answer per object is what makes the difference between ~2000 dropped records and the few dozen that genuinely need dropping.

Safety

  • Refuses to run where fewer than two R minors are installed. With nothing to compare, every verdict would read "safe" — writing a bucket full of false clearances is far worse than writing none.
  • Skips packages with no compiled code: they cannot fail this way and are never dropped, so downloading them to prove it is wasted.
  • Resumable; already-recorded objects cost one query.

crow lint passes.

Sequencing

Merge and release bincraft #128 first, then run this per slot, then re-index. Verifying the drop counts fall is the point of the exercise, so that re-index is where the fix is confirmed rather than assumed.

## Why bincraft #128 lets `union_index_records()` keep a carried flat record when that specific object is recorded safe. On its own it changes nothing for the records already being dropped, because they are legacy objects with no verdict: ``` 4.4: 16398 dropped across 9 indexes (~1822 each) 4.5: 20076 dropped across 9 indexes (~2230 each) 4.6: 15532 dropped across 9 indexes (~1726 each) ``` Every one of those resolves to CRAN source for clients the routing serves correctly. This backfill inspects the objects already in the bucket and writes their verdicts, which is what actually restores the coverage. ## How the verdict is made By symbols, not heuristics. A binary fails at `dyn.load()` under minor M exactly when it references a symbol M does not export, and the volatile set is derived by diffing the `libR.so` exports of the R builds installed in the image — so a new R release updates it with no curated list to maintain. That matters here because the source-level classifier calls **93%** of compiled packages risky (`LinkingTo: Rcpp` alone is enough), while a symbol scan of the same slot found **17** packages that break under 4.6 and **72** under 4.4. Recording the real answer per object is what makes the difference between ~2000 dropped records and the few dozen that genuinely need dropping. ## Safety - **Refuses to run where fewer than two R minors are installed.** With nothing to compare, every verdict would read "safe" — writing a bucket full of false clearances is far worse than writing none. - Skips packages with no compiled code: they cannot fail this way and are never dropped, so downloading them to prove it is wasted. - Resumable; already-recorded objects cost one query. `crow lint` passes. ## Sequencing Merge and release bincraft #128 first, then run this per slot, then re-index. Verifying the drop counts fall is the point of the exercise, so that re-index is where the fix is confirmed rather than assumed.
union_index_records() keeps a carried flat record when that object is recorded
safe and drops it otherwise. Objects uploaded before that record existed have no
verdict, so they are dropped. A full re-index removed roughly 2000 records per
per-minor index -- 16398 across the 4.4 indexes, 20076 across 4.5, 15532 across
4.6 -- each resolving to CRAN source for clients the routing serves correctly.

This inspects the objects already in the bucket and writes the verdict, which is
what restores that coverage.

The question is decided by symbols rather than heuristics: a binary fails at
dyn.load() under minor M exactly when it references a symbol M does not export,
and the volatile set is derived by diffing the R builds installed in the image.

This change will:

- add local/backfill-flat-safety.R, inspecting each compiled flat object and
  recording whether it loads under every installed minor
- refuse to run where fewer than two R minors are installed, since every verdict
  would then read "safe" for want of anything to compare
- skip packages with no compiled code, which cannot fail this way
- add a manual .crow workflow over the same slot matrix as reindex.yaml
pat-s merged commit c216e44578 into main 2026-09-05 09:35:47 +00:00
pat-s deleted branch feat/backfill-flat-object-safety 2026-09-05 09:35:48 +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!202
No description provided.