Files
Magnus HedemarkandGitHub 865a1e90c6 feat: fix remaining agent readiness signals for level 5
Squash merge. All CI passes.

4 signals fixed: min_release_age, issue_labeling_system, error_to_insight_pipeline, deployment_observability.
2026-07-29 18:33:55 -04:00

122 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Check that pinned dependencies have a minimum release age.
Reads requirements-dev.txt and verifies each ==-pinned package
was released at least MIN_AGE_DAYS ago on PyPI. Packages without
a == pin (using >= or no version) are skipped.
Used in CI to enforce a minimum adoption delay for new releases,
mitigating supply chain attacks from compromised new releases.
"""
from __future__ import annotations
import json
import re
import sys
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
REQUIREMENTS = ROOT / "requirements-dev.txt"
MIN_AGE_DAYS = 7
PYPI_API = "https://pypi.org/pypi/{package}/json"
def parse_pinned_deps(path: Path) -> list[tuple[str, str]]:
"""Extract (package, version) for ==-pinned dependencies."""
pinned: list[tuple[str, str]] = []
pin_pattern = re.compile(r"^([a-zA-Z0-9_.-]+(?:\[[^\]]*\])?)==([^;]+)")
for line in path.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
match = pin_pattern.match(line)
if match:
name = match.group(1).split("[")[0]
version = match.group(2).strip()
pinned.append((name, version))
return pinned
def get_release_date(package: str, version: str) -> datetime | None:
"""Fetch the upload date for a specific package version from PyPI.
Returns None if the version or package cannot be found.
"""
url = PYPI_API.format(package=package)
try:
with urllib.request.urlopen(url, timeout=15) as resp: # nosec B310
data = json.loads(resp.read().decode("utf-8"))
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError):
print(f" WARNING: Could not fetch metadata for {package}", file=sys.stderr)
return None
releases = data.get("releases", {})
version_files = releases.get(version, [])
if not version_files:
print(f" WARNING: Version {version} of {package} not found on PyPI", file=sys.stderr)
return None
upload_time_str = version_files[0].get("upload_time", "")
if not upload_time_str:
return None
try:
return datetime.fromisoformat(upload_time_str).replace(tzinfo=timezone.utc)
except ValueError:
return None
def main() -> int:
if not REQUIREMENTS.exists():
print(f"ERROR: {REQUIREMENTS} not found", file=sys.stderr)
return 1
deps = parse_pinned_deps(REQUIREMENTS)
if not deps:
print("No ==-pinned dependencies found. Nothing to check.")
return 0
now = datetime.now(timezone.utc)
cutoff = now - timedelta(days=MIN_AGE_DAYS)
violations: list[tuple[str, str, datetime]] = []
print(f"Checking {len(deps)} pinned dependencies for {MIN_AGE_DAYS}+ day release age...\n")
for package, version in deps:
release_date = get_release_date(package, version)
if release_date is None:
print(f" {package}=={version}: skipped (could not determine release date)")
continue
age_days = (now - release_date).days
status = "OK" if release_date <= cutoff else "TOO NEW"
print(
f" {package}=={version}: released {release_date.date()} ({age_days}d ago) - {status}"
)
if release_date > cutoff:
violations.append((package, version, release_date))
if violations:
print(
f"\nERROR: {len(violations)} pinned package(s) released less than {MIN_AGE_DAYS} days ago:"
)
for pkg, ver, date in violations:
age = (now - date).days
print(f" - {pkg}=={ver} (released {date.date()}, only {age}d ago)")
print(
f"\nPolicy requires a minimum of {MIN_AGE_DAYS} days between a PyPI release and adoption."
)
print("This reduces supply chain risk from compromised new releases.")
print("See docs/dependency-policy.md for details.")
return 1
print(f"\nAll pinned dependencies are at least {MIN_AGE_DAYS} days old. Policy satisfied.")
return 0
if __name__ == "__main__":
sys.exit(main())