#!/usr/bin/env python3 """Generate a deterministic dependency/build-input inventory. The inventory is intentionally offline and reproducible. It records only inputs that are committed in this repository: exact Python pins, container image tags, the immutable upstream commit, and runtime version declarations. External vulnerability scanners consume this inventory in CI, but generating it never requires network access. """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] def _noncomment_lines(path: Path) -> list[str]: return [ line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip() and not line.lstrip().startswith("#") ] def build_inventory(root: Path = ROOT) -> dict[str, Any]: dockerfile = (root / "Dockerfile").read_text(encoding="utf-8") requirements = _noncomment_lines(root / "requirements.lock") ci_requirements = _noncomment_lines(root / "requirements-ci.lock") python_version = (root / ".python-version").read_text(encoding="utf-8").strip() package_json = json.loads((root / "package.json").read_text(encoding="utf-8")) package_lock = json.loads((root / "package-lock.json").read_text(encoding="utf-8")) images = [] for raw in re.findall(r"^FROM\s+([^\s]+)", dockerfile, re.MULTILINE): images.append(raw) upstream = re.search( r"^ARG HERMES_UPSTREAM_REF=([0-9a-f]{40})$", dockerfile, re.MULTILINE ) if not upstream: raise ValueError("Dockerfile does not contain an immutable upstream commit") for requirement in [*requirements, *ci_requirements]: if "==" not in requirement or any(op in requirement for op in (">=", "<=", "~=", "!=", "*")): raise ValueError(f"non-exact dependency pin: {requirement}") return { "schemaVersion": 1, "project": "Hermes Futures Desk V5", "runtime": { "python": python_version, "node": package_json["engines"]["node"], "npm": package_json["engines"]["npm"], }, "upstream": { "repository": "https://github.com/NousResearch/hermes-agent.git", "commit": upstream.group(1), }, "pythonDependencies": sorted(requirements), "ciPythonDependencies": sorted(ci_requirements), "containerImages": images, "nodeDependencyPolicy": { "upstreamLockfile": "package-lock.json", "installCommand": "npm ci --prefer-offline --no-audit", "localLockfile": "package-lock.json", "localLockfileVersion": package_lock.get("lockfileVersion"), "localNodeDependencies": sorted((package_json.get("dependencies") or {}).items()), "localNodeDevDependencies": sorted((package_json.get("devDependencies") or {}).items()), }, } def render(inventory: dict[str, Any]) -> str: return json.dumps(inventory, indent=2, sort_keys=True) + "\n" def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", default=str(ROOT)) parser.add_argument("--output") parser.add_argument("--check", action="store_true") args = parser.parse_args(argv) root = Path(args.root).resolve() output = Path(args.output).resolve() if args.output else root / "docs" / "dependency_inventory.json" try: rendered = render(build_inventory(root)) except (OSError, ValueError) as exc: print(f"ERROR: {exc}", file=sys.stderr) return 1 if args.check: try: current = output.read_text(encoding="utf-8") except OSError as exc: print(f"ERROR: dependency inventory missing: {exc}", file=sys.stderr) return 1 if current != rendered: print("ERROR: dependency inventory is stale", file=sys.stderr) return 1 print(f"dependency_inventory=PASS path={output}") return 0 output.parent.mkdir(parents=True, exist_ok=True) output.write_text(rendered, encoding="utf-8") print(f"wrote {output}") return 0 if __name__ == "__main__": raise SystemExit(main())