feat(slots): add a retirement workflow and a drift check

Retiring a slot means three separate things in three separate places, and
nothing tied them together.
Alpine 3.22 is what that costs: it was removed from `local.rpkgs_slots` in
`cdn.tf` at some point, so the edge stopped routing it, but it kept a matrix
row in twelve pipelines, six cron jobs on the Crow server and ~50000 objects in
the bucket.
Nightly builds kept producing binaries nobody could reach, and because an
unrouted slot serves the flat index to every client, R 4.4 and R 4.6 users got
`LOAD_FAILED` rather than nothing.

This change will:

- Add `local/check-slots.R`, which fails when any `.crow/*.yaml` still targets
  a slot missing from `local.rpkgs_slots`. Run it with `just check-slots`.
- Add `local/retire-slot.R`, which strips a slot from the pipeline matrices and
  deletes its objects from the bucket. Dry run by default, and it refuses any
  slot still listed in `cdn.tf`, so it cannot be pointed at a live slot.
- Add `.crow/retire-slot.yaml`, which runs the asset half in CI where the B2
  credentials exist.
- Document the ordering in README, including the cron jobs, which live on the
  Crow server and cannot be removed from here.

The check is one-directional on purpose.
A pipeline targeting an unpublished slot is an error; a published slot missing
from *some* pipeline is only reported, because several pipelines cover a subset
deliberately.

Verified against the tree as it stood before #213: the checker names exactly the
twelve files that PR edited by hand, and `retire-slot.R --config-only` reproduces
that commit's structural changes byte for byte across all 18 pipeline files.
The only differences are two prose lines, a usage example and a dropdown
`description:`, which the script leaves alone because rewriting free text
automatically is guesswork.
This commit is contained in:
Patrick Schratz 2026-09-11 08:17:21 +00:00
commit bfe56ffd22
No known key found for this signature in database
GPG key ID: 62050D5BC68AB6DC

View file

@ -198,6 +198,42 @@ Example: <https://cran.devxy.io/arm64/rhel9/latest>
Technically, no date-based snapshots are planned, so this component from the Posit PM structure is not included.
## Retiring a slot
A slot is only reachable by clients if it appears in `local.rpkgs_slots` in `cdn.tf`.
That list feeds the `UNION_SLOTS` variable and `edge/rpkgs-router.ts` gates all per-R-minor index rewriting on it.
A slot missing from it is not merely unadvertised, it is broken: every request falls through to the flat index, which holds a single build per package, so clients on any R minor other than the one the slot was built against die at `dyn.load()`.
Alpine 3.22 sat in that state for months, built nightly and never routed.
Retire a slot in this order:
1. Remove it from `local.rpkgs_slots` in `cdn.tf` and from `DistroId` in the website's `src/src/data/repos.ts`, then `tofu apply`.
This is the decision; everything below is cleanup.
2. Strip it from the pipeline matrices and commit the result:
```sh
# dry run
just retire-slot alpine322
# write the changes
just retire-slot alpine322 --apply
```
3. Delete its cron jobs on the Crow server.
They are not in this repository, and removing a matrix row does not remove the cron that targets it.
`local/retire-slot.R` prints the ones it finds, with the API call.
4. Delete its objects from the bucket, which needs the B2 credentials and therefore runs in CI:
```sh
crow pipeline create devxy/build-cran-binaries --branch main \
--var retire_slot=alpine322 --var RETIRE_APPLY=yes
```
`local/retire-slot.R` refuses any slot still listed in `cdn.tf`, so steps 2 and 4 cannot run against a live slot.
Doing step 4 before step 2 is the one ordering that actively breaks things: the next nightly would rebuild everything just deleted.
`just check-slots` fails when any pipeline still targets a slot the edge does not route, which is the drift that put Alpine 3.22 where it was.
It is one-directional on purpose: a published slot missing from _some_ pipeline is only reported, because several pipelines cover a subset deliberately.
## Helpers
Helper scripts are located in `local/`.