0011 — One propagation gate for every producer
- Status: Accepted
- Date: 2026-09-04
Context
bioc-registry decides what propagates into the served repositories. Until now it decided in two places with two vocabularies:
- r-universe builds:
evaluate()in the Worker — build status, per-family check verdicts on the gating R line, strict version bump, then the dependency fixpoint (approveByDeps, bioc-registry #34). Blocked packages were written toprop/{u}/blocked/with the unmet requirement. - bioc-build builds (SPEC-014):
scripts/publish.sh— sha256, attestation, manifest authorization atmanifest_commit, and its own version gate (strict bump, or replacing abioconductorseed at the same version). The dependency gate did not apply at all.POST /publishtrusted the publisher’s verdict.
Two gates drift. The seed-replacement rule already differed between them, the dependency rule was missing from one, and “why did X not propagate” needed the reader to know which producer built X and then re-derive the answer from shell or TypeScript.
Decision
One pure function, gate(inputs, config, index) in bioc-registry src/repo.ts, takes build details and configuration and returns, per candidate, a yes/no and every rule’s verdict:
{ propagate, archs, reasons: [{rule, ok, detail}] }
rules: build-status, families, bioccheck, version-parse, version-gate,
manifest-state, manifest-git-url, manifest-stream, manifest-component (bioc-build only),
deps (a fixpoint over the whole wave, run last)
config: { gating_r, bioccheck: advisory|blocking, replace_seed }
Three callers, no other gating anywhere:
evaluate()gates each r-universe wave withreplace_seed: falseand the gating R from bioconductor.orgconfig.yaml.POST /publishgates each bioc-build candidate withreplace_seed: true(SPEC-014: a seed is not a verdict; our build of that version is) and the R the policy’s image ships (bioc-build #32: drift is accepted, not gated on). The publisher sendsstaged.jsonverbatim; the route fetches the manifest facts atmanifest_commit, recordsrejected:<rule>itself, and answers with the decision. Anentrywithoutstagedis accepted only as a byte-identical re-POST of the already-accepted record (the self-heal path).POST /gateis the same function, read-only, against the live index, for anyone asking “would this propagate, and why not”.
Integrity stays with the producer’s bytes: sha256 and gh attestation verify remain in publish.sh. They prove the tarball is bioc-build’s; they are not propagation policy.
prop/{u}/blocked/ now records every new version that failed, with its full reasons array, not only dependency blocks.
Consequences
- One vocabulary for
attempts.jsonstatuses:rejected:<rule>where rule is a gate rule name, plus the three integrity checks. - bioc-build packages are now subject to the dependency gate. Experiment-data packages mostly depend on CRAN or on software already in the index, so this is expected to bite rarely, and when it does the reason is recorded.
- A rule change is one edit with one test file (
src/repo.test.ts), and the publisher shrinks by the gating it no longer performs. - SPEC-014’s publisher algorithm (bioc-build) and bioc-registry
docs/api.mdare updated in the same change.