Ask JitPack metadata last for names no other repo knows and that
are not com.github.* (whose metadata is 401, tags used instead).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWY1k4muZfdVJfb7YGJ
toolchain:python's "latest" was platform.python_version(), so the verdict
depended on the machine running the gate: setup-python "3.x" gave kuhyx/spellcode
3.15.0 on one runner and 3.14.8 on another, making its transitive:pyodide
allowlist entry dead (exit 2) on one and required (exit 1) on the other.
Now registries/toolchain.python_latest() reads python.org's release API
(is_published, pre_release=false), keeps only exact "Python X.Y.Z" names (the
feed also lists "Python install manager 26.4"), and takes the newest stable.
The never_cached() carve-out is gone: it existed because a cached
*interpreter* version from an older runner image (3.13.15) was served to a
3.14.7 job in screen-locker's CI. A registry answer is safe to cache, so
python now goes through the normal TTL cache, prefetch, and offline fallback
(expired cache -> DEGRADED; no cache -> unavailable), never the local
interpreter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4VrLZn9YjhPuoxTj54WKk
A `pkg @ git+https://github.com/<owner>/<repo>@pkg-vX.Y.Z` pin outside
kuhyx/utils was asked of utils, which has no such tag, so
steam-backlog-enforcer's leisure-watch pin was undeterminable (exit 3).
Foreign pins are now named `<host>/<owner>/<repo>:<pkg>`; utils pins keep
their bare names (allowlist labels and cache keys unchanged).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
Three findings from proving the JitPack fix on the TachiyomiSY gate:
- The first repository with a stable answer used to win. Central still
carries com.github.ben-manes:gradle-versions-plugin 0.11.1 (2016) while
the plugin portal has 0.54.0, so a 0.51.0 pin graded "current". Every
repository is now asked and the newest stable across the pooled lists is
the answer.
- ~900 lookups, eight at a time, each resolving the registry host (A+AAAA)
per connection: the LAN router's forwarder started dropping queries for
every client on the LAN and the gate degraded itself. socket.getaddrinfo
is memoised per (host, port, hints) for the run, and the per-host probe
runs under a lock so eight workers make one probe, not eight.
- A resolver EAI_AGAIN ("temporary failure") during the probe marked the
registry unreachable for the whole run. The probe retries it
HTTP_ATTEMPTS times; a permanent failure still fails immediately.
Both retry loops lost their unreachable `return` sentinel (http.py is at
100% branch now); the resolver tests live in test_http_resolver.py to keep
test_http.py under the 250-line cap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P4KZw1E2sXJjYqfcZWf1x
Since ~2026-09-14 https://jitpack.io/.../maven-metadata.xml answers HTTP 401
"No access token" (artifact POM/AAR downloads still 200), which turned every
com.github.* coordinate into "undeterminable" (exit 3) in --strict CI.
JitPack is dropped from MAVEN_REPOS. A com.github.<owner>:<repo> name that no
HTTP repository knows is now answered from the GitHub repo's tags via
`git ls-remote --tags --refs` -- JitPack's version IS the tag name, so the
tags are compared as they are (v prefix included). The tag list goes through
the same stable-first selector as maven-metadata, so a newer pre-release tag
does not win. Not an allowlist entry, not a token.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P4KZw1E2sXJjYqfcZWf1x
The gate had no way to read a Gradle build, which left the two Kotlin
forks (tachiyomisy, syncyomi-src) outside the fleet-wide rule.
- parsers/gradle.py reads gradle/libs.versions.toml ([libraries] and
[plugins] in every shape: inline string, module + version.ref,
group/name/version, rich {strictly|require|prefer} tables) into
`maven:<group>:<artifact>` deps, pointing the fix at the [versions]
line when a ref is used; BOM-managed entries without a version are
skipped. gradle-wrapper.properties becomes `toolchain:gradle`.
- registries/maven.py asks Google Maven, Central and the plugin portal
in that order, reading the full <versions> list -- AndroidX writes
alphas into <latest>. Plugins resolve through their marker artifact.
- registries/toolchain.gradle_latest reads services.gradle.org.
- http.get_text shares the transport with get_json (XML bodies).
- Templates: the pre-commit `files:` and the CI cache key know the
catalog. 100% branch on every new file; fixtures only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Yc4TiPjhbQLfH35QBPfrbP
"Are we on the newest version?" was not mechanically answerable across these
repos: constraint styles had drifted per-ecosystem and a caret range is
"newest-compatible" by definition, which gives a checker nothing to compare.
This gate makes it answerable and then enforces the answer.
Checks two things at once -- that every dependency carries an exact pin, and
that the pin equals its registry's latest STABLE release -- across pub.dev,
PyPI, npm, crates.io, the Go proxy, kuhyx/utils git tags, and the Flutter /
Node / Python toolchains.
Three deliberate design points:
* Pre-releases are rejected per-ecosystem, because every ecosystem leaks them
differently and each leak fails GREEN. npm lets a maintainer point
`dist-tags.latest` at an rc; crates.io's `max_version` includes them; PyPI's
legacy `info.version` includes yanked releases. Unit-tested with fixtures,
since an end-to-end run cannot surface a wrong "up to date".
* Offline, the gate serves an expired cache entry and PASSES with a DEGRADED
banner (exit 3 only under --strict, which is CI). `--no-verify` is banned
here, so a hook that hard-failed offline would leave no way to commit at all.
* The allowlist has two entry classes. `blocked_by: transitive:<pkg>@<ver>` is
a predicate that clears itself when upstream moves and must NOT carry a
date; everything else is discretionary and requires `expires`, capped at 90
days. Exact pins plus latest-everywhere is provably unsatisfiable when a
dependency internally constrains a shared transitive, and expiring the
predicate class on a calendar would turn the gate red for a reason no commit
could fix. An entry with nothing left to excuse is itself an error.
One hook id, one shim, one shared implementation -- always delegating, so it
cannot fork the way the 250-line cap forked into four ids, two of which were
independent reimplementations.
256 tests, 100% branch coverage on every module of the gate. Also adds the
workflow that runs them, which incidentally gives md_naming's 71 tests their
first CI run.
Not yet installed in any repo: a repo that gets the gate before its deps are
bumped is instantly and permanently red, so the gate and the upgrade land
together, per repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK