mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-11 19:47:12 +03:00
Squash merge. All CI passes. 4 signals fixed: min_release_age, issue_labeling_system, error_to_insight_pipeline, deployment_observability.
122 lines
4.1 KiB
Python
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())
|