#!/usr/bin/env python3 """Fail while a declared or pinned toolchain version is EOL, or has fallen out of the N-1 band. ``pnpm audit`` and ``pip-audit`` see published *packages*. They are blind to the toolchain binaries FlintTrade pins itself - the Node runtime and pnpm release the desktop bootstrap downloads, the uv build that provisions Python, and the floors and targets ``flint.toml`` declares. Those go stale silently: nothing in CI noticed that the pinned Node release key had expired, and nothing today notices when a pinned runtime line reaches end of life. This check reads ``flint.toml`` as the source of truth and compares every version it finds against upstream release data: * ``https://nodejs.org/dist/index.json`` - every published Node release * ``.../nodejs/Release/main/schedule.json`` - the Node LTS/EOL calendar * ``https://registry.npmjs.org/-/package/pnpm/dist-tags`` - the pnpm release tags * ``https://api.github.com/repos/astral-sh/uv/releases/latest`` - the current uv * ``https://api.github.com/repos/ollama/ollama/releases/latest`` - the current stable Ollama * ``https://endoflife.date/api/python.json`` - the CPython EOL calendar Two severities, and the distinction mirrors the one ``flint.toml`` itself draws: **Floors** (``python_requires``, ``node_requires``) are set by what the dependency tree genuinely needs, so they are judged on end of life alone. A floor that trails the newest release is correct behaviour, not drift. **Targets and pins** (``python_target``, ``node_target``, the bootstrap ``tool-manifest.json`` entries, ``packageManager``) are meant to track current stable, so they fail when the line is EOL *or* has slipped further back than one release line (the N-1 band). Being behind on the patch inside an in-band line is a warning, because a pin like Node's travels with a mirrored signing key and cannot move on its own. For Node, released and supported Current lines count alongside LTS lines inside the same N-1 band. A scheduled future line or prerelease does not count. Floors still follow end of life only; choosing a Current target does not raise them. The managed Ollama pin (``_OLLAMA_VERSION`` in ``ollama_runtime.py``) is advisory only: it carries per-platform hashes that are regenerated by hand, so falling more than ``_OLLAMA_MINOR_LINES_BEHIND_WARN`` minor versions behind the latest stable release is a warning and never a failure. Network failures never fail the run: each source is fetched independently and a source that cannot be reached is reported as skipped. A blip upstream must not redden an unrelated pull request:: python scripts/check-toolchain-freshness.py python scripts/check-toolchain-freshness.py --offline # parse the pins, fetch nothing python scripts/check-toolchain-freshness.py --json # machine-readable findings """ from __future__ import annotations import argparse import dataclasses import datetime as dt import json import pathlib import re import sys import tomllib import urllib.error import urllib.request from typing import Any _REPO_ROOT = pathlib.Path(__file__).resolve().parents[1] _FLINT_TOML = _REPO_ROOT / "flint.toml" _TOOL_MANIFEST = _REPO_ROOT / "packages/apps/desktop/resources/bootstrap/tool-manifest.json" _ROOT_PACKAGE_JSON = _REPO_ROOT / "package.json" _OLLAMA_RUNTIME = _REPO_ROOT / "packages/core/core/src/flinttrade_core/ollama_runtime.py" _NODE_DIST_URL = "https://nodejs.org/dist/index.json" _NODE_SCHEDULE_URL = "https://raw.githubusercontent.com/nodejs/Release/main/schedule.json" _PNPM_DIST_TAGS_URL = "https://registry.npmjs.org/-/package/pnpm/dist-tags" _UV_LATEST_RELEASE_URL = "https://api.github.com/repos/astral-sh/uv/releases/latest" _PYTHON_CYCLES_URL = "https://endoflife.date/api/python.json" # ``/releases/latest`` never returns a draft or a pre-release, so it is the stable channel. _OLLAMA_LATEST_RELEASE_URL = "https://api.github.com/repos/ollama/ollama/releases/latest" _FETCH_TIMEOUT_SECONDS = 20 # The npm registry and the GitHub API both want a User-Agent; the default # urllib one is rejected by some CDN edges. _USER_AGENT = "flinttrade-toolchain-freshness/1 (+https://github.com/navaneeshnagarajan/FlintTrade)" # How many release lines back from current still counts as supported here. 1 means # "the newest line and the one before it" - the N-1 band. _LINES_BACK_ALLOWED = 1 # The managed Ollama pin is warn-only. Ollama is pre-1.0, so its release line is the # minor; warn once the pin trails the latest stable release by more than this many. _OLLAMA_MINOR_LINES_BEHIND_WARN = 2 _OLLAMA_PIN_PATTERN = re.compile( r'^_OLLAMA_VERSION(?:\s*:\s*[^=\n]+)?\s*=\s*"(?Pv\d+\.\d+\.\d+)"\s*$', re.MULTILINE, ) @dataclasses.dataclass(frozen=True) class Waiver: """A knowingly-out-of-band pin, with a date the waiver stops working. Attributes: subject: The subject string the finding is raised against. reason: Why the pin cannot move yet, in one sentence. review_by: ISO date after which the waiver expires and the finding fails. """ subject: str reason: str review_by: str # Waivers are deliberately dated. An undated suppression is indistinguishable # from a bug, so every entry expires and the finding turns fatal again on its own. # # Empty on purpose. The previous entry covered the pnpm 9 to 11 migration and # expired on 2026-10-31. That migration has landed: the pin is pnpm 12, the # current release line, so the out-of-band finding no longer applies. A patch # behind ``latest-12`` stays a warning. Add a Waiver only for a pin that is # knowingly outside the N-1 band, with a future ``review_by`` and a reason # that says why an upgrade cannot land yet. _WAIVERS: tuple[Waiver, ...] = () @dataclasses.dataclass(frozen=True) class Finding: """One thing this check has to say about a version. Attributes: level: ``fail``, ``warn`` or ``note``. subject: What the finding is about, e.g. ``node_target (flint.toml)``. message: The operator-facing explanation. """ level: str subject: str message: str class Report: """Collected findings plus the sources that could not be reached.""" def __init__(self) -> None: self.findings: list[Finding] = [] self.skipped: list[str] = [] def add(self, level: str, subject: str, message: str) -> None: """Record a finding, applying any live waiver. Args: level: ``fail``, ``warn`` or ``note``. subject: What the finding is about. message: The operator-facing explanation. """ if level == "fail": waiver = _live_waiver(subject) if waiver is not None: self.findings.append( Finding( "warn", subject, f"{message} WAIVED until {waiver.review_by}: {waiver.reason}.", ) ) return self.findings.append(Finding(level, subject, message)) def skip(self, source: str, error: str) -> None: """Record an unreachable data source. Args: source: The URL that could not be read. error: The failure text. """ self.skipped.append(f"{source} ({error})") @property def failed(self) -> bool: """Whether any finding is fatal.""" return any(finding.level == "fail" for finding in self.findings) def _live_waiver(subject: str) -> Waiver | None: """Return the unexpired waiver covering *subject*, if any. Args: subject: The finding subject. Returns: The waiver, or ``None`` when there is none or it has expired. """ today = dt.date.today() for waiver in _WAIVERS: if waiver.subject != subject: continue if dt.date.fromisoformat(waiver.review_by) >= today: return waiver return None def _fetch_json(url: str, report: Report) -> Any | None: """Fetch and parse JSON, recording a skip rather than raising. Args: url: The source to read. report: The report that records unreachable sources. Returns: The parsed document, or ``None`` when the source could not be read. """ request = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT, "Accept": "application/json"}) try: with urllib.request.urlopen(request, timeout=_FETCH_TIMEOUT_SECONDS) as response: return json.loads(response.read().decode("utf-8")) except (urllib.error.URLError, TimeoutError, ValueError, OSError) as error: report.skip(url, str(error)) return None def _requirements() -> dict[str, str]: """Return ``flint.toml``'s ``[requirements]`` table. Returns: The declared floors and targets. """ with _FLINT_TOML.open("rb") as handle: return {key: str(value) for key, value in tomllib.load(handle)["requirements"].items()} def _major(version: str) -> int: """Return the leading integer of a version or range string. Args: version: A value such as ``">=22.22.0"``, ``"24"`` or ``"v22.23.2"``. Returns: The major component. Raises: ValueError: When no leading integer can be found. """ match = re.search(r"(\d+)", version) if match is None: raise ValueError(f"no version number in {version!r}") return int(match.group(1)) def _version_tuple(version: str) -> tuple[int, ...]: """Return a comparable tuple for a dotted version. Args: version: A dotted version, optionally ``v``-prefixed. Returns: The numeric components. """ return tuple(int(part) for part in re.findall(r"\d+", version.lstrip("v"))) def _parse_schedule_date(value: str | None) -> dt.date | None: """Parse a Node schedule date, tolerating the ``YYYY-MM`` form. Args: value: The raw date field, if present. Returns: The date, or ``None`` when absent or unparseable. """ if not value: return None for pattern in ("%Y-%m-%d", "%Y-%m"): try: return dt.datetime.strptime(value, pattern).date() except ValueError: continue return None def _band_message(subject: str, line: object, allowed: list[str]) -> str: """Render the standard out-of-band explanation. Args: subject: What is out of band. line: The release line the subject sits on. allowed: The lines inside the N-1 band, newest first. Returns: The message text. """ rendered = ", ".join(allowed) return ( f"{subject} sits on line {line}, which is further back than N-1. " f"Supported lines today are {rendered}. Move the target/pin forward, or widen the band deliberately." ) def _check_node(report: Report, requirements: dict[str, str], pinned_node: str | None) -> None: """Check the Node floor, target and bootstrap pin against upstream release data. Args: report: The report to record findings on. requirements: ``flint.toml``'s ``[requirements]`` table. pinned_node: The bootstrap ``tool-manifest.json`` Node version, if readable. """ schedule = _fetch_json(_NODE_SCHEDULE_URL, report) releases = _fetch_json(_NODE_DIST_URL, report) if not isinstance(schedule, dict): return today = dt.date.today() lifecycles: dict[int, dict[str, dt.date | None]] = {} for raw_line, entry in schedule.items(): if not isinstance(entry, dict): continue try: major = _major(raw_line) except ValueError: continue lifecycles[major] = { "start": _parse_schedule_date(entry.get("start")), "lts": _parse_schedule_date(entry.get("lts")), "maintenance": _parse_schedule_date(entry.get("maintenance")), "end": _parse_schedule_date(entry.get("end")), } # Current is stable before it enters LTS. Count it only after both its # scheduled start and an actual stable publication, never from the future # LTS date alone. Keep the same two-line N-1 band, not LTS plus an exception. published_lines = ( { _major(entry["version"]) for entry in releases if isinstance(entry, dict) and isinstance(entry.get("version"), str) and re.fullmatch(r"v\d+\.\d+\.\d+", entry["version"]) and (published := _parse_schedule_date(entry.get("date"))) is not None and published <= today } if isinstance(releases, list) else set() ) supported_lines = sorted( major for major, dates in lifecycles.items() if (dates["end"] is None or dates["end"] > today) and ( (dates["lts"] is not None and dates["lts"] <= today) or (dates["start"] is not None and dates["start"] <= today and major in published_lines) ) ) band = supported_lines[-(_LINES_BACK_ALLOWED + 1) :] def report_unavailable(subject: str, major: int) -> bool: if major not in lifecycles: report.add("fail", subject, f"Node {major} is not in the upstream release schedule.") return True dates = lifecycles[major] if dates["start"] is not None and dates["start"] > today: report.add("fail", subject, f"Node {major} is not yet released (starts {dates['start']}).") return True if report_eol(subject, major): return True if dates["lts"] is None or dates["lts"] > today: if not isinstance(releases, list): report.skip(_NODE_DIST_URL, f"cannot establish publication of Current Node {major}") return True if dates["start"] is None or major not in published_lines: report.add("fail", subject, f"Node {major} has no published stable release as of {today}.") return True return False def end_of_life(major: int) -> dt.date | None: return lifecycles.get(major, {}).get("end") def report_eol(subject: str, major: int) -> bool: end = end_of_life(major) if end is not None and end <= today: report.add("fail", subject, f"Node {major} reached end of life on {end:%Y-%m-%d}.") return True if major not in lifecycles: report.add("warn", subject, f"Node {major} is not in the upstream release schedule.") return True return False floor_major = _major(requirements["node_requires"]) if not report_eol("node_requires (flint.toml)", floor_major): dates = lifecycles[floor_major] maintenance = dates["maintenance"] end = dates["end"] phase = "maintenance" if maintenance is not None and maintenance <= today else "active" report.add( "note", "node_requires (flint.toml)", f"Node {floor_major} floor is in {phase} support" + (f" until {end:%Y-%m-%d}" if end is not None else "") + ". A floor is judged on end of life only - it tracks what the dependency tree needs.", ) target_major = _major(requirements["node_target"]) if not report_unavailable("node_target (flint.toml)", target_major) and band and target_major not in band: report.add( "fail", "node_target (flint.toml)", _band_message(f"Node target {target_major}", target_major, [str(item) for item in band[::-1]]), ) if pinned_node is None: return pinned_major = _major(pinned_node) subject = "node (bootstrap tool-manifest.json)" if report_unavailable(subject, pinned_major): return if band and pinned_major not in band: report.add( "fail", subject, _band_message(f"The pinned Node {pinned_node}", pinned_major, [str(item) for item in band[::-1]]), ) return if not isinstance(releases, list): return line_releases = [ entry["version"] for entry in releases if isinstance(entry, dict) and isinstance(entry.get("version"), str) and re.fullmatch(r"v\d+\.\d+\.\d+", entry["version"]) and ( # Preserve established LTS index handling, but a Current pin must # carry its own publication date, not borrow another pin's evidence. ( entry.get("date") is None and (lts := lifecycles[pinned_major]["lts"]) is not None and lts <= today ) or ((published := _parse_schedule_date(entry.get("date"))) is not None and published <= today) ) and _major(entry["version"]) == pinned_major ] if not re.fullmatch(r"\d+\.\d+\.\d+", pinned_node) or f"v{pinned_node}" not in line_releases: report.add("fail", subject, f"Node {pinned_node} is not a published stable release as of {today}.") return newest = max(line_releases, key=_version_tuple) if _version_tuple(newest) > _version_tuple(pinned_node): report.add( "warn", subject, f"The bootstrap pins Node {pinned_node} but {newest.lstrip('v')} is the newest {pinned_major}.x release. " "Regenerate the manifest (the mirrored signing key travels with the version, so this is never a " "hand edit).", ) def _check_pnpm(report: Report, pinned_pnpm: str | None, package_manager: str | None) -> None: """Check the pnpm pin against the registry's release tags. Args: report: The report to record findings on. pinned_pnpm: The bootstrap ``tool-manifest.json`` pnpm version, if readable. package_manager: The root ``packageManager`` field, if readable. """ if pinned_pnpm is None: return if package_manager is not None and not package_manager.startswith(f"pnpm@{pinned_pnpm}+"): report.add( "fail", "pnpm (bootstrap pin vs packageManager)", f"packageManager is {package_manager!r} but the bootstrap manifest pins pnpm {pinned_pnpm}. " "The desktop bootstrap enables the same pnpm the repo does, so the two must agree.", ) subject = "pnpm (release line)" tags = _fetch_json(_PNPM_DIST_TAGS_URL, report) if not isinstance(tags, dict): return latest = tags.get("latest") if not isinstance(latest, str): return latest_major = _major(latest) pinned_major = _major(pinned_pnpm) band = list(range(latest_major - _LINES_BACK_ALLOWED, latest_major + 1)) if pinned_major not in band: report.add( "fail", subject, _band_message(f"pnpm {pinned_pnpm}", pinned_major, [str(item) for item in band[::-1]]), ) return newest_in_line = tags.get(f"latest-{pinned_major}") if isinstance(newest_in_line, str) and _version_tuple(newest_in_line) > _version_tuple(pinned_pnpm): report.add( "warn", subject, f"pnpm {pinned_pnpm} is pinned but {newest_in_line} is the newest {pinned_major}.x release.", ) def _check_uv(report: Report, pinned_uv: str | None) -> None: """Check the uv pin against the newest published release. Args: report: The report to record findings on. pinned_uv: The bootstrap ``tool-manifest.json`` uv version, if readable. """ if pinned_uv is None: return subject = "uv (bootstrap tool-manifest.json)" release = _fetch_json(_UV_LATEST_RELEASE_URL, report) if not isinstance(release, dict): return tag = release.get("tag_name") if not isinstance(tag, str): return latest = _version_tuple(tag) pinned = _version_tuple(pinned_uv) if not latest or not pinned: return # uv is pre-1.0, so its release *line* is the minor, not the major. line_index = 1 if latest[0] == 0 else 0 if pinned[:line_index] != latest[:line_index]: report.add("fail", subject, _band_message(f"uv {pinned_uv}", pinned_uv, [tag.lstrip("v")])) return lines_back = latest[line_index] - pinned[line_index] if lines_back > _LINES_BACK_ALLOWED: report.add("fail", subject, _band_message(f"uv {pinned_uv}", pinned_uv, [tag.lstrip("v")])) return if pinned < latest: report.add("warn", subject, f"uv {pinned_uv} is pinned but {tag.lstrip('v')} is the newest release.") def _check_python(report: Report, requirements: dict[str, str]) -> None: """Check the Python floor and target against the CPython EOL calendar. Args: report: The report to record findings on. requirements: ``flint.toml``'s ``[requirements]`` table. """ cycles = _fetch_json(_PYTHON_CYCLES_URL, report) if not isinstance(cycles, list): return lifecycles: dict[tuple[int, ...], dt.date | None] = {} for entry in cycles: if not isinstance(entry, dict) or not isinstance(entry.get("cycle"), str): continue lifecycles[_version_tuple(entry["cycle"])] = _parse_schedule_date( entry["eol"] if isinstance(entry.get("eol"), str) else None ) if not lifecycles: return today = dt.date.today() live = sorted(cycle for cycle, eol in lifecycles.items() if eol is None or eol > today) band = live[-(_LINES_BACK_ALLOWED + 1) :] def report_eol(subject: str, cycle: tuple[int, ...]) -> bool: eol = lifecycles.get(cycle) if eol is not None and eol <= today: rendered = ".".join(str(part) for part in cycle) report.add("fail", subject, f"Python {rendered} reached end of life on {eol:%Y-%m-%d}.") return True return False floor = _version_tuple(requirements["python_requires"])[:2] if not report_eol("python_requires (flint.toml)", floor) and floor in lifecycles: eol = lifecycles[floor] report.add( "note", "python_requires (flint.toml)", f"Python {'.'.join(str(part) for part in floor)} floor is supported" + (f" until {eol:%Y-%m-%d}" if eol is not None else "") + ". A floor is judged on end of life only.", ) target = _version_tuple(requirements["python_target"])[:2] if not report_eol("python_target (flint.toml)", target) and band and target not in band: rendered = [".".join(str(part) for part in cycle) for cycle in band[::-1]] report.add( "fail", "python_target (flint.toml)", _band_message(f"Python target {'.'.join(str(part) for part in target)}", target, rendered), ) def _local_pins(report: Report) -> tuple[str | None, str | None, str | None, str | None]: """Read the pins this repo controls. Args: report: The report to record parse failures on. Returns: The bootstrap Node, pnpm and uv versions plus the root ``packageManager``. """ node = pnpm = uv = package_manager = None try: manifest = json.loads(_TOOL_MANIFEST.read_text(encoding="utf-8")) node = str(manifest["node"]["version"]) pnpm = str(manifest["pnpm"]["version"]) uv = str(manifest["uv"]["version"]) except (OSError, KeyError, ValueError) as error: report.add("fail", "bootstrap tool-manifest.json", f"could not read the pinned versions: {error}") try: package_manager = str(json.loads(_ROOT_PACKAGE_JSON.read_text(encoding="utf-8"))["packageManager"]) except (OSError, KeyError, ValueError) as error: report.add("fail", "package.json", f"could not read packageManager: {error}") return node, pnpm, uv, package_manager def _ollama_pin(report: Report) -> str | None: """Read the managed Ollama pin from the runtime module, as it is on this checkout. Args: report: The report to record parse failures on. Returns: The pinned version (for example ``v0.32.0``), or ``None`` when unreadable. """ try: text = _OLLAMA_RUNTIME.read_text(encoding="utf-8") except OSError as error: report.add("fail", "ollama_runtime.py", f"could not read the managed Ollama pin: {error}") return None match = _OLLAMA_PIN_PATTERN.search(text) if match is None: report.add( "fail", "ollama_runtime.py", 'could not find a literal `_OLLAMA_VERSION = "vX.Y.Z"` assignment. ' "Renovate's regex manager and this check both read that constant, so keep it a plain string literal.", ) return None return match.group("version") def _check_ollama(report: Report, pinned_ollama: str | None) -> None: """Warn when the managed Ollama pin trails the latest stable release. This never fails: the pin carries per-platform hashes that are regenerated by hand, so being behind is a work order, not a broken build. Args: report: The report to record findings on. pinned_ollama: The ``_OLLAMA_VERSION`` pin, if readable. """ if pinned_ollama is None: return subject = "ollama (managed runtime pin)" release = _fetch_json(_OLLAMA_LATEST_RELEASE_URL, report) if not isinstance(release, dict) or release.get("prerelease") is True or release.get("draft") is True: return tag = release.get("tag_name") if not isinstance(tag, str): return latest = _version_tuple(tag) pinned = _version_tuple(pinned_ollama) if len(latest) < 2 or len(pinned) < 2: return # Ollama is pre-1.0, so its release line is the minor. A major move counts as out of range. if pinned[0] < latest[0]: lines_back = None elif pinned[0] > latest[0]: return else: lines_back = latest[1] - pinned[1] if lines_back is None or lines_back > _OLLAMA_MINOR_LINES_BEHIND_WARN: behind = "a newer major version" if lines_back is None else f"{lines_back} minor versions" report.add( "warn", subject, f"The managed runtime pins Ollama {pinned_ollama} but the latest stable release is {tag} " f"({behind} ahead; warning beyond {_OLLAMA_MINOR_LINES_BEHIND_WARN}). " "The per-platform hashes in ollama_runtime.py are regenerated by hand, never bumped in place.", ) def _render(report: Report, pins: dict[str, str | None]) -> None: """Print the human-readable report. Args: report: The findings to print. pins: The local pins, for the header. """ print("Declared and pinned toolchain") for label, value in pins.items(): print(f" {label:<28} {value or '(unreadable)'}") print() order = {"fail": 0, "warn": 1, "note": 2} icons = {"fail": "FAIL", "warn": "WARN", "note": "note"} for finding in sorted(report.findings, key=lambda item: order[item.level]): print(f"{icons[finding.level]}: {finding.subject}: {finding.message}") if report.skipped: print() print("Sources not reached (checked skipped, not failed):") for source in report.skipped: print(f" - {source}") print() if report.failed: print("RESULT: fail - a declared or pinned toolchain version is EOL or out of band.") else: print("RESULT: ok - every reachable check passed.") def main() -> int: """Run the freshness checks. Returns: ``1`` when any finding is fatal, otherwise ``0``. """ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--offline", action="store_true", help="Parse the local pins and fetch nothing.") parser.add_argument("--json", action="store_true", help="Emit findings as JSON instead of text.") parser.add_argument( "--report", type=pathlib.Path, help="Also write the JSON findings here, so one run can produce both the log and the artefact.", ) args = parser.parse_args() report = Report() node, pnpm, uv, package_manager = _local_pins(report) ollama = _ollama_pin(report) requirements = _requirements() pins = { "python_requires (floor)": requirements.get("python_requires"), "python_target": requirements.get("python_target"), "node_requires (floor)": requirements.get("node_requires"), "node_target": requirements.get("node_target"), "node (bootstrap pin)": node, "pnpm (bootstrap pin)": pnpm, "uv (bootstrap pin)": uv, "ollama (managed pin)": ollama, "packageManager": package_manager, } if args.offline: report.add( "note", "network", "--offline: the local pins parsed, but no upstream release data was consulted.", ) else: _check_node(report, requirements, node) _check_pnpm(report, pnpm, package_manager) _check_uv(report, uv) _check_ollama(report, ollama) _check_python(report, requirements) document = json.dumps( { "generated": dt.datetime.now(tz=dt.UTC).isoformat(timespec="seconds"), "pins": pins, "findings": [dataclasses.asdict(finding) for finding in report.findings], "skipped": report.skipped, "failed": report.failed, }, indent=2, ) if args.report is not None: args.report.write_text(document + "\n", encoding="utf-8") if args.json: print(document) else: _render(report, pins) return 1 if report.failed else 0 if __name__ == "__main__": sys.exit(main())