Harden the provider per Momus review and prove it with offline fixtures

bin/sync-upstream self-test swaps the two network fetches in
helpers/upstream-github.sh for fixture readers and runs the production code
paths: fallback past a quarantined release, draft/prerelease filtering, the
deliberate bypass, unchanged-version and all-quarantined no-update paths,
unusable tags/timestamps and missing checksums failing the sync, {tag} and
{pkgver} asset templates with ./ and * manifest prefixes across both
architectures, the min_release_age backstop verdicts (now a testable
release_age_status function), the duration parser, and manifest validation.

Also fixes from the review: the duration parser forces base-10 arithmetic
(leading zeros no longer parse as octal) and bounds values to nine digits so
no suffix can overflow; jq // treating false as absent can no longer let
"min_release_age": false or "upstream": false slip through as unset; the
release feed page grew to the API maximum of 100 with the bounded search
documented; and the README package-metadata section documents the upstream
block, min_release_age, the bypass, and provider-versus-hook exclusivity.
This commit is contained in:
Ryan Hughes
2026-08-24 20:07:10 -04:00
parent 699261471a
commit 5777573a84
4 changed files with 294 additions and 35 deletions
+23 -6
View File
@@ -21,7 +21,24 @@
package_upstream_github_repo() {
local pkgdir="$1"
package_metadata_value "$pkgdir" '.upstream.github' ""
# `objects` drops a non-object upstream value (validation rejects those
# separately) instead of erroring the jq pipeline.
package_metadata_value "$pkgdir" '(.upstream? | objects | .github)' ""
}
# Fetches sit behind functions so the self-test can replace them with fixture
# readers; everything below the fetch is deterministic and testable offline.
# Only the 100 most recent releases are considered -- a bounded search, not
# pagination. A feed whose entire first page is drafts, prereleases, or
# quarantined releases reports no update and waits for the next run.
github_fetch_releases() {
local repo="$1"
curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=100"
}
github_fetch_checksums() {
local repo="$1" tag="$2" asset="$3"
curl -fsSL "https://github.com/$repo/releases/download/$tag/$asset"
}
# Emits the newest qualifying release as hook-contract JSON. min_release_age
@@ -35,18 +52,18 @@ github_upstream_release() {
local metadata repo checksums_name
metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '.upstream.github // ""' "$metadata")
repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata")
if [[ ! "$repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2
return 1
fi
checksums_name=$(jq -r '.upstream.checksums // ""' "$metadata")
checksums_name=$(jq -r '(.upstream? | objects | .checksums) // ""' "$metadata")
if [[ -z "$checksums_name" ]]; then
echo "upstream.checksums names the checksum manifest asset and is required" >&2
return 1
fi
local arches
mapfile -t arches < <(jq -r '.upstream.assets // {} | keys[]' "$metadata")
mapfile -t arches < <(jq -r '(.upstream? | objects | .assets) // {} | keys[]' "$metadata")
if [[ ${#arches[@]} -eq 0 ]]; then
echo "upstream.assets must map at least one architecture to an asset name" >&2
return 1
@@ -54,7 +71,7 @@ github_upstream_release() {
local releases now
now=$(date +%s)
if ! releases=$(curl -fsSL "https://api.github.com/repos/$repo/releases?per_page=20"); then
if ! releases=$(github_fetch_releases "$repo"); then
echo "could not fetch the release feed for $repo" >&2
return 1
fi
@@ -108,7 +125,7 @@ github_upstream_release() {
fi
local checksums
if ! checksums=$(curl -fsSL "https://github.com/$repo/releases/download/$best_tag/$checksums_name"); then
if ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then
echo "could not fetch $checksums_name for $repo $best_tag" >&2
return 1
fi